Spring Security - 我爱学习网 (5axxw.com)
底层是filter,通过一些列filter,对请求进行处理。
组件
SecurityContext :上下文对象,存储认证后的authenation 。
SecurityContextHolder :用于获取SecurityContext 的工具类。
SecurityContext :认证接口,定义了认证对象的数据形式。
AuthenticationManager :用于校验Authentication,返回一个认证完成后的Authentication对象,默认实现类是ProviderManager。
Authentication : 用户的认证信息,有三个核心属性:
- principal: 用户的身份信息
- credentails: 用户认证凭据,比如密码。认证完成后,此项会被清空。
- authorities:用户权限。
Authentication的两个主要作用:
- 为 AuthenticationManager 对象提供用于认证的信息载体;
- 用于获取某个用户的基本信息。
在 Spring Boot 方式下启动 Spring Security 工程,将会自动开启如下配置项:
默认开启一系列基于 springSecurityFilterChain 的 Servlet 过滤器,包含了几乎所有的安全功能,例如:保护系统 URL、验证用户名、密码表单、重定向到登录界面等;
创建 UserDetailsService 实例,并生成随机密码,用于获取登录用户的信息详情;
将安全过滤器应用到每一个请求上。
配置类
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private UserDetailsService userDetailsService;
@Override
public void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(new JwtAuthenticationProvider(userDetailsService));
}
@Bean
@Override
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
//使用的是JWT,禁用csrf
httpSecurity.cors().and().csrf().disable()
//设置请求必须进行权限认证
.authorizeRequests()
//跨域预检请求
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
//permitAll()表示所有用户可认证
.antMatchers( "/webjars/**").permitAll()
//首页和登录页面
.antMatchers("/").permitAll()
.antMatchers("/login").permitAll()
// 验证码
.antMatchers("/captcha.jpg**").permitAll()
// 其他所有请求需要身份认证
.anyRequest().authenticated();
//退出登录处理
httpSecurity.logout().logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler());
//token验证过滤器
httpSecurity.addFilterBefore(new JwtAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class);
}
}
实现UserDetailsService
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserService userService;
@Autowired
private RoleInfoService roleInfoService;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
log.debug("开始登陆验证,用户名为: {}",s);
// 根据用户名验证用户
QueryWrapper queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(UserInfo::getLoginAccount,s);
UserInfo userInfo = userService.getOne(queryWrapper);
if (userInfo == null) {
throw new UsernameNotFoundException("用户名不存在,登陆失败。");
}
// 构建UserDetail对象
UserDetail userDetail = new UserDetail();
userDetail.setUserInfo(userInfo);
List roleInfoList = roleInfoService.listRoleByUserId(userInfo.getUserId());
userDetail.setRoleInfoList(roleInfoList);
return userDetail;
}
}
认证过程中SpringSecurity会调用这个方法查询出用户,验证登录用户信息是否正确。将我们查询出来的用户信息和权限信息组装成一个UserDetails返回。
TokenUtil
采用JWT的认证模式,所以我们也需要一个帮我们操作Token的工具类,一般来说它具有以下三个方法就够了:
创建token
验证token
反解析token中的信息
做JWT认证需要我们自己写一个过滤器来做JWT的校验,然后将这个过滤器放在UsernamePasswordAuthenticationFilter前面。
@Override
protected void doFilterInternal(@NotNull HttpServletRequest request,
@NotNull HttpServletResponse response,
@NotNull FilterChain chain) throws ServletException, IOException {
log.info("JWT过滤器通过校验请求头token进行自动登录...");
// 拿到Authorization请求头内的信息
String authToken = jwtProvider.getToken(request);
// 判断一下内容是否为空且是否为(Bearer )开头
if (StrUtil.isNotEmpty(authToken) && authToken.startsWith(jwtProperties.getTokenPrefix())) {
// 去掉token前缀(Bearer ),拿到真实token
authToken = authToken.substring(jwtProperties.getTokenPrefix().length());
// 拿到token里面的登录账号
String loginAccount = jwtProvider.getSubjectFromToken(authToken);
if (StrUtil.isNotEmpty(loginAccount) && SecurityContextHolder.getContext().getAuthentication() == null) {
// 缓存里查询用户,不存在需要重新登陆。
UserDetail userDetails = caffeineCache.get(CacheName.USER, loginAccount, UserDetail.class);
// 拿到用户信息后验证用户信息与token
if (userDetails != null && jwtProvider.validateToken(authToken, userDetails)) {
// 组装authentication对象,构造参数是Principal Credentials 与 Authorities
// 后面的拦截器里面会用到 grantedAuthorities 方法
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities());
// 将authentication信息放入到上下文对象中
SecurityContextHolder.getContext().setAuthentication(authentication);
log.info("JWT过滤器通过校验请求头token自动登录成功, user : {}", userDetails.getUsername());
}
}
}
chain.doFilter(request, response);
}
从请求中取出token->解析token->token认证->security认证
登录->拿到token->请求带上token->JWT过滤器拦截->校验token->从缓存里面拿我们的UserDetail->
组装一个authentication对象,把它放在上下文对象中,这样后面的过滤器看到我们上下文对象中有authentication对象,就相当于我们已经认证过了