Spring Security

Spring Security - 我爱学习网 (5axxw.com)

底层是filter,通过一些列filter,对请求进行处理。


2021013117322096.png

组件

SecurityContext :上下文对象,存储认证后的authenation 。

SecurityContextHolder :用于获取SecurityContext 的工具类。

SecurityContext :认证接口,定义了认证对象的数据形式。

AuthenticationManager :用于校验Authentication,返回一个认证完成后的Authentication对象,默认实现类是ProviderManager。
Authentication : 用户的认证信息,有三个核心属性:

  1. principal: 用户的身份信息
  2. credentails: 用户认证凭据,比如密码。认证完成后,此项会被清空。
  3. authorities:用户权限。

Authentication的两个主要作用:

  1. 为 AuthenticationManager 对象提供用于认证的信息载体;
  2. 用于获取某个用户的基本信息。
6a3c9e78-c6f5-4df6-8317-84863d3b135c.png

在 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对象,就相当于我们已经认证过了

你可能感兴趣的:(Spring Security)