Spring Security 是 Spring 家族中的一个安全管理框架。相比与另外一个安全框架Shiro,它提供了更丰富的功能,社区资源也比Shiro丰富。
1.环境搭建
1. 引入Spring Security依赖
2.当前项目中的所有资源都被springSecurity管理
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Spring Security非常重要的配置类,一定要有,不然下面的流程都无法进行
/**
* spring security配置
*/
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* token认证过滤器
*/
@Autowired
private JwtAuthenticationTokenFilter authenticationTokenFilter;
/**
* 解决 无法直接注入 AuthenticationManager,只有配置了这个bean,登录接口那里才能用
* @return
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**过滤器,过滤登录请求不拦截*/
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// CSRF禁用,因为不使用session
.csrf().disable()
// 认证失败处理类
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// 基于token,所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// 过滤请求
.authorizeRequests()
// 对于登录login 验证码captchaImage 允许匿名访问
.antMatchers("/login").anonymous()
//放行前端
.antMatchers(
HttpMethod.GET,"/**/*.html",
"/**/*.css", "/**/*.js").permitAll()
// 添加JWT filter
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// 添加CORS filter
httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
}
/**
* 强散列哈希加密实现,只有配置了这个Bean,验证密码的时候才能指定使用的是BCrypt加盐加密
*/
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder()
{
return new BCryptPasswordEncoder();
}
}
返回的UserDetails的实现类
@Data
public class LoginUser implements UserDetails
{
/**
* 用户唯一标识
*/
private String token;
/**
* 用户信息
*/
private User user;
public LoginUser()
{
}
public LoginUser(SysUser user, Set<String> permissions)
{
this.user = user;
this.permissions = permissions;
}
}
②user实体类
@Data
public class SysUser extends BaseEntity
{
/** 用户ID */
private Long userId;
/** 用户账号 */
private String userName;
/** 密码 */
private String password;
public SysUser()
{
}
public SysUser(Long userId)
{
this.userId = userId;
}
前端发送请求给我们的登录接口
@Component
public class SysLoginService {
@Autowired
private RedisCache redisCache;
public String login(String username, String password, String code, String uuid) {
// 用户验证
/**
* !!!看我看我看我!!!
*看我少走弯路,获取用户对象的时候,会去调用下面的这个方法查询用户对象
* 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
*/
System.out.println("username "+username+" -----password "+password);
Authentication authentication = authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(username, password));
//判断是否为空,如果为空则代表登陆失败验证错误
if(Object.isNull(authentication)){
有写自定义异常就抛异常,没有就输出控制台
}
User User = (User) authentication.getPrincipal();
// 生成token
return tokenService.createToken(loginUser);
}
重写的UserDetailsServiceImpl实现类
/**
* 用户验证处理
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
/**这个查询数据库用户表的业务层持久层自己写,太简单我就不写了,文章重点不在这*/
@Autowired
private ISysUserService userService;
/**此处重写了使用的loadUserByUsername方法,这样就不会走默认的查询内存的方法
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//查询此用户名的数据库信息
SysUser user = userService.selectByUserName(username);
if (StringUtils.isNull(user)) {
写了自定义异常就在这里抛出异常(用户不存在),没写就写个输出语句输出控制台
}
else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
写了自定义异常就在这里抛出异常(用户已被删除),没写就写个输出语句输出控制台
}
return new LoginUser(user,null);
}
}
以及redis的代码,登录查询用户表的业务层和持久层等,这些大都一样,我就不写了
2. 底层原理
在项目资源前,添加了一个过滤器链内部包含了提供各种功能的过滤器
3. 核心过滤器
1负责身份验证的过滤器
UsernamePasswordAuthenticationFilter
2.负责权限校验的过滤器(Spring Security另一功能鉴权使用的)
FilterSecurityInterceptor
Spring Security登录的大致流程
前端携带账号密码验证码发送请求,服务器与数据库中的对比如果正确就生成一个jwt返回给前端
具体流程请看这张图
前端的请求会发送到UsernamePasswordAuthenticationFilter类上,然后封装成Authentication对象,此时还没有权限,然后将对象依次向后发送,我们只需要关注最后一个类
Spring Security的UserDetailService接口类默认会从内存中查找数据,我们需要重写这个接口来实现从数据库中查找数据.
前端发送请求给登录接口,登录接口会将数据传给经过重写的UserDetailsServiceImpl类,
UserDetailsServiceImpl类将查询到的用户数据封装返回给负责身份验证的过滤器
UsernamePasswordAuthenticationFilter
这个过滤器不需要我们写,是jar包自带的,它会自动帮我们判断密码是否正确,这里因为我们在SecurityConfig配置了使用BCrypt加盐加密(这里我建议创建账号的时候密码的加密方式使用Bcript加盐加密,这种加密方式是不可逆不宜破解的,安全性较高,每次生成的加密字段都不相同)
如果密码正确,那么将生成JWT(JWT是token具体的一种实现方式,本质上就是一个字符串,可以理解为一种支持集群/分布式/微服务架构的一种session,他不存储在服务器,而是存储在客户端)
生成JWT我们可以使用JJWT工具,导入pom依赖(jwt可以说就是token)
<!--Token生成与解析-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
编写一个Token类来使用这个依赖,类中使用redis,是因为Spring Security的另一功能鉴权需要使用数据,所以存入Redis中以备使用
@Component
public class TokenService {
@Autowired
private RedisCache redisCache;
/**
* 创建令牌
* @param loginUser 用户信息
* @return 令牌
*/
public String createToken(User user) {
String token = user.getId;
loginUser.setToken(token);
setUserAgent(user);
refreshToken(user);
Map<String, Object> claims = new HashMap<>();
claims.put("login_user_key", token);
//存放非敏感信息
claims.put("username",loginUser.getUsername());
return createToken(claims);
}
/**
* 从数据声明生成令牌
*
* @param claims 数据声明
* @return 令牌
*/
private String createToken(Map<String, Object> claims)
{
String token = Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, secret).compact();
return token;
}
登录接口的最后一点生成token使用的就是这个Token类生成的token
至此,使用Spring Security 的登录操作就结束了,token是Spring Security的另一大功能,鉴权索要使用的,及登陆完成后进行其他操作时鉴权所使用的数据
以上就是我对Spring Security的登录操作的个人理解,本人尚且才疏学浅,描述的不是很详细,如果各位想了解的更详细可以去看B站的三更草堂的视频.