一、Spring Security概述
1,Spring Security简介
Spring Security 是一个能够为基于 Spring 的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在 Spring 应用上下文中配置的 Bean,充分利用了 Spring IoC(Inversion of Control 控制反转),DI(Dependency Injection 依赖注入)和 AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
Spring Security 拥有以下特性:
- 对身份验证和授权的全面且可扩展的支持
- 防御会话固定、点击劫持,跨站请求伪造等攻击
- 支持 Servlet API 集成
- 支持与 Spring Web MVC 集成
Spring、Spring Boot 和 Spring Security 三者的关系如下图所示:
2、Spring Security快速入门
2.1、引入依赖
<!--springboot整合security坐标-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
或者是在创建springboot项目时勾选
2.2、创建一个控制器
@RestController
public class HelloController {
@GetMapping("hello")
public String hello(){
return "Hello Spring security";
}
}
2.3、启动项目
访问:http://localhost:8080/hello 结果打开的是一个登录页面,其实这时候我们的请求已经被保护起来了,要想访问,需要先登录。
Spring Security 默认提供了一个用户名为 user 的用户,其密码在控制台可以找到
四、Spring Security 认证配置
1、WebSecurityConfigurerAdapter
当然还可以通过配置类的方式进行配置,创建一个配置类去继承,实现自定义用户名密码登录
/**
* Spring Security配置类
* 在springboot2.7 后WebSecurityConfigurerAdapter弃用了,用2.5.4
*/
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 对密码进行加密。123 是密码明文,现在 Spring Security 强制性要求『不允许明文存储密码』。
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String password = passwordEncoder.encode("123");
auth.inMemoryAuthentication().withUser("tom").password(password).roles("admin");
}
}
springsecurity强制性要求必须使用密码加密器(PasswordEncoder)对原始密码(注册密码)进行加密。因此,如果忘记指定 PasswordEncoder 会导致执行时会出现 There is no PasswordEncoder mapped for the id "null"
异常。
这是因为我们在对密码加密的时候使用到了 BCryptPasswordEncoder 对象,而 Spring Security 在对密码比对的过程中不会『自己创建』加密器,因此,需要我们在 Spring IoC 容器中配置、创建好加密器的单例对象,以供它直接使用。
所以,我们还需要在容器中配置、创建加密器的单例对象(上面那个 new 理论上可以改造成注入),修改Spring securitry配置类
/**
* Spring Security配置类
* 在springboot2.7后WebSecurityConfigurerAdapter弃用了,用2.5.4
*/
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 对密码进行加密。123 是密码明文,现在 Spring Security 强制性要求『不允许明文存储密码』。
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String password = passwordEncoder.encode("123");
//此方法是在代码中写死的,无法从数据库中获取,所以基本不适用
auth.inMemoryAuthentication().withUser("tom").password(password).roles("admin");
}
/**
* 将PasswordEncoder注入到ioc容器
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Spring Security 内置的 Password Encoder 有:
加密算法名称 | PasswordEncoder |
NOOP | NoOpPasswordEncoder.getInstance() |
SHA256 | new StandardPasswordEncoder() |
BCRYPT(官方推荐) | new BCryptPasswordEncoder() |
LDAP | new LdapShaPasswordEncoder() |
PBKDF2 | new Pbkdf2PasswordEncoder() |
SCRYPT | new SCryptPasswordEncoder() |
MD4 | new Md4PasswordEncoder() |
MD5 | new MessageDigestPasswordEncoder(“MD5”) |
SHA_1 | new MessageDigestPasswordEncoder(“SHA-1”) |
SHA_256 | new MessageDigestPasswordEncoder(“SHA-256”) |
2、从数据库中获取信息
- 在pom中添加依赖
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<!-- mysql-connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.32</version>
</dependency>
- 在yml中配置
server:
port: 8070
spring:
# 配置数据源信息
datasource:
# 配置数据源类型
type: com.zaxxer.hikari.HikariDataSource
# 配置连接数据库信息
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/security?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
username: root
password: 123456
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.woniu.entity
configuration:
# 配置MyBatis日志,执行sql的时候,将sql打印到控制台
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
- 在entity包下新建实体类Users,建议不要用User,因为UserDetails 接口中有一个实现类叫User,不止一次会导错包
package com.woniu.entity;
import lombok.Data;
import java.util.List;
@Data
public class Users {
private Integer id;
private String username;
private String account;
private String password;
private List<String> anths;//该用户拥有的权限
}
- 在数据库中新建表
User表
关联表
权限表 - 在mapper包下创建UsersMapper
package com.woniu.dao;
import com.woniu.entity.Users;
public interface UsersDao {
/**
* 通过账号获取用户信息
* @param account
* @return
*/
Users getUserInfoByAccount(String account);
}
- 在resources下新建mapper包,创建UsersMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.woniu.mapper.UsersMapper">
<resultMap id="userMap" type="Users">
<result column="id" property="id"></result>
<result column="username" property="username"></result>
<result column="account" property="account"></result>
<result column="password" property="password"></result>
<collection property="anths" ofType="java.lang.String">
<result column="anth_code"></result>
</collection>
</resultMap>
<select id="getUserInfoByAccount" resultMap="userMap">
SELECT
us.id,
us.username,
us.account,
us.password,
ta.anth_code
FROM
t_users us
left join t_user_anth tua on us.id = tua.user_id
left join t_anth ta on tua.anth_id = ta.id
where account = #{account}
</select>
</mapper>
- 在service包下创建一个MyUserDetailsService 类实现UserDetailsService 接口
/**
* spring security认证业务类
*/
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UsersMapper usersMapper;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//调用dao到数据库中根据username查找用户信息
Users users = usersMapper.getByUserName(username);
String anths = String.join(",", userInfo.getAnths());
try {
//将查找到的用户帐号与密码保存到Security的User对象中由Security进行比对
return new User(users.getUsername(), passwordEncoder.encode(users.getPassword()),
//配置登录用户有哪些角色和权限,此处模拟直接写死
AuthorityUtils.commaSeparatedStringToAuthorityList(anths);
}catch (Exception e){
throw new UsernameNotFoundException("用户"+username+"不存在");
}
}
}
- 修改spring security配置类
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
/**
* 将PasswordEncoder注入到ioc容器
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
- 启动项目,在浏览器中测试