springsecurity使用:登录与校验

首先是引入依赖
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

默认方案:

首次使用这个空项目的时候他会给你一个默认的账号

账号名为user

密码在控制台输出如下图

 这时再进入后端会直接弹到一个/login的地址,输入上述账号密码即可查看路由的具体内容,也可以输出/logout退出登录

 

springsercurity本质是一个过滤器链

 在默认案例中UsernamePasswordAuthenticationFilter中会调用UserDetailService接口的InMemoryUserDetailManager获取用户信息 ,后续我们需要修改成在数据库中查询信息而非内存中

 

 

自拟方案:

登录

  1. 自定义登录接口:调用ProviderManager认证,通过则生成jwt,并把用户数据存入redis,自定义UserDetailService,把从内存查数据改成从数据库查
    1. 自定义service继承UserDetailService
      public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
              //查询用户信息
              User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getUserName, username));
              if(Objects.isNull(user)){
                  throw new RuntimeException("用户名或密码错误");
              }
              String password = user.getPassword();
              //todo 查询权限信息
              //封装返回
              LoginUser loginUser=new LoginUser();
              loginUser.setUser(user);
              return loginUser;
          }
    2. 实现LoginUser类,继承的UserDetails
      public class LoginUser implements UserDetails 
    3. 这里如果进行测试的话,会报错:PasswordEncoder会拿获得的密码和数据库比对,但是要求数据库内的格式为{id}password,根据id去判断加密方式。所以我们写的时候会用BC替换掉这个方法
      @Configuration
      public class SecurityConfig extends WebSecurityConfigurerAdapter {
          @Bean
          public PasswordEncoder passwordEncoder(){
              return new BCryptPasswordEncoder();
          }
      
      }

      注:这里使用了bc加密之后数据库的密码也应在添加时替换成加密后的密码.bc提供了matches()和encode()两种方法

    4. 添加拦截配置,给登录方法放行一下
       @Override
          protected void configure(HttpSecurity http) throws Exception {
              http
                      .csrf().disable()
                      .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
      
                      .authorizeRequests()
                      .antMatchers("/user/login").anonymous() //匿名访问:未授权前可以使用,授权之后就不再可以使用
                      .anyRequest().authenticated(); // 必须要授权才可以使用
          }
    5. 登录service
      public ResponseResult login(User user) {
              //进行用户认证
              UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                      new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());
              Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
              if (Objects.isNull(authenticate)) {
                  throw new RuntimeException("用户名或密码错误");
              }
              LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
              String id = loginUser.getUser().getId().toString();
              String jwt = JwtUtil.createJWT(id);
              HashMap<String, String> map = new HashMap<>();
              map.put("token", jwt);
              redisCache.setCacheObject("login:"+id,loginUser);
              return new ResponseResult("登录成功", 200, map);
          }
  2. 校验:获取token后解析获取userid,通过redis获取其中用户数据,并将其存入SecurityContextHolder中
    1. 配置过滤器
      @Component
      public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
          @Autowired
          RedisCache redisCache;
          @Override
          protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
              //获取token并解析
              String token = request.getHeader("token");
              if (!StringUtils.hasText(token)) {
                  filterChain.doFilter(request, response);
                  return;
              }
              String userId;
              try {
                  Claims claims = JwtUtil.parseJWT(token);
                  userId = claims.getSubject();
              } catch (Exception e) {
                  throw new RuntimeException(e);
              }
              //从redis中获取用户信息再存入securityContextHolder
              String redisKey = "login:" + userId;
              LoginUser loginUser = redisCache.getCacheObject(redisKey);
              if(Objects.isNull(loginUser)){
                   throw new RuntimeException("用户未登录");
              }
              //todo 权限信息还没写
              SecurityContextHolder.getContext().
                      setAuthentication(new UsernamePasswordAuthenticationToken(loginUser,null,null));
              filterChain.doFilter(request, response);
          }
      }
    2. 添加过滤器
      @Override
          protected void configure(HttpSecurity http) throws Exception {
              http
                      .addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
          }

       

为什么要存入信息到SecurityContextHolder中:后续步骤都需要从其中取数据,如果是未认证的状态会导致后面不放行

为什么上述两个位置同样用到了UsernamePasswordAuthenticationToken方法但作用不同:Authentication表示当前访问系统的用户,封装了用户相关信息。

在第一个构造函数中,1.5登录service:我们使用了AuthenticationManager.authenticate()这一方法对数据进行逻辑验证,验证失败则返回结果为null

而第二个构造函数中,2.1配置过滤器:这里手动设置安全信息,此时已经通过安全检查了

 

//config的一些参数使用案例
 .authorizeRequests()
                .antMatchers("/user/login").anonymous()//匿名访问,是指在未授权前可以使用,授权后就不能再使用此功能了
                .antMatchers("/example").permitAll()//什么状态都可以访问
                .anyRequest().authenticated();//授权后才可以访问

 

 

posted @ 2024-07-21 18:37  天启A  阅读(483)  评论(0)    收藏  举报