java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringSecurity 认证

SpringSecurity 认证实现流程分析

作者:每天进步一......

SpringSecurity框架通过一个过滤器链来处理认证和授权,主要包括UsernamePasswordAuthenticationFilter负责处理登录请求,本文给大家介绍SpringSecurity 认证实现登录校验,感兴趣的朋友跟随小编一起看看吧

一、初步理解

SpringSecurity的原理其实就是一个过滤器链,内部包含了提供各种功能的过滤器。

当前系统中SpringSecurity过滤器链中有哪些过滤器及它们的顺序。

核心过滤器:

二、Token(Jwt)登录校验流程

三、具体认证授权细节

下图是UsernamePasswordAuthenticationFilter处理用户名、密码,然后将用户名、密码、权限信息封装到Authentication对象中,再放到SecurityContextHolder中。

Authentication接口: 它的实现类,表示当前访问系统的用户,封装了用户相关信息。

AuthenticationManager接口:定义了认证Authentication的方法

UserDetailsService接口:加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的 方法。

UserDetails接口:提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装 成UserDetails对象返回。然后将这些信息封装到Authentication对象中。

认证

授权

自定义登录认证接口:①调用ProviderManager的方法进行认证;②如果认证通过生成jwt;③把用户信息存入redis中

自定义权限信息查询:在UserDetailsService这个实现类中去查询数据库

四、自定义权限查询

修改UsernamePasswordAuthenticationFilter上图最右边的授权部分。

 1.自定义登陆接口

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;
    @PostMapping("/login")
    public R login(@RequestBody User user) {
        String jwt = userService.login(user);
        if (StringUtils.hasLength(jwt)) {
            return R.ok().message("登陆成功").data("token", jwt);
        }
        return R.error().message("登陆失败");
    }
}

 2.配置数据库校验登录用户

从之前的分析我们可以知道,我们可以自定义一个UserDetailsService,让SpringSecurity使用我们的 UserDetailsService。我们自己的UserDetailsService可以从数据库中查询用户名和密码。

创建一个类实现UserDetailsService接口,重写loadUserByUsername方法

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
     @Autowired
     private UserMapper userMapper;
     @Override
     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
         //查询用户信息
         QueryWrapper<User> queryWrapper=new QueryWrapper<>();
         queryWrapper.eq("user_name",username);
         User user = userMapper.selectOne(queryWrapper);
         //如果没有查询到用户,就抛出异常
         if(Objects.isNull(user)){
             throw  new RuntimeException("用户名或密码错误");
         }
         //TODO 查询用户对应的权限信息
         细节见SpringSecurity(二)——授权实现
         //如果有,把数据封装成UserDetails对象返回
         return new LoginUser(user);
    }
}

五、Jwt认证过滤器(自定义过滤器)

(1)在接口中我们通过AuthenticationManager的authenticate方法来进行用户认证,所以需要在 SecurityConfig中配置把AuthenticationManager注入容器。

@EnableWebSecurity
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig{
    /**
     * 登录时需要调用AuthenticationManager.authenticate执行一次校验
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }
}

 (2)登录的业务逻辑层实现类

第一次登录,生成jwt存入redis

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public String login(User user) {
        //1.封装Authentication对象 ,密码校验,自动完成
        UsernamePasswordAuthenticationToken authentication =
                new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());
        //2.进行校验
        Authentication authenticate = authenticationManager.authenticate(authentication);
        //3.如果authenticate为空
        if (Objects.isNull(authenticate)) {
            throw new RuntimeException("登录失败"); //TODO 登录失败
        }
        //4.得到用户信息
        LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
        //生成jwt,使用fastjson的方法,把对象转成字符串
        String loginUserString = JSON.toJSONString(loginUser);
        //调用JWT工具类,生成jwt令牌
        String jwt = JwtUtils.createJWT(loginUserString, null);
        //5.把生成的jwt存到redis
        String tokenKey = "token_" + jwt;
        stringRedisTemplate.opsForValue().set(tokenKey, jwt, JwtUtils.JWT_TTL / 1000);
        Map<String, Object> map = new HashMap<>();
        map.put("token", jwt);
        map.put("username", loginUser.getUsername());
        return jwt;
    }
}

(3)jwt认证校验过滤器

我们需要自定义一个过滤器,这个过滤器会去获取请求头中的token,对token进行解析取出其中的 userid。 使用userid去redis中获取对应的LoginUser对象。

然后封装Authentication对象存入SecurityContextHolder

/**
 * token验证过滤器   //每一个servlet请求,只会执行一次
 */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Autowired
    private LoginFailureHandler loginFailureHandler;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain filterChain)
            throws ServletException, IOException {
        try {
            //1.获取当前请求的url地址
            String url = request.getRequestURI();
            //如果当前请求不是登录请求,则需要进行token验证
            if (!url.equals("/user/login")) {
                //2.验证token
                this.validateToken(request);
            }
        } catch (AuthenticationException e) {
            System.out.println(e);
            loginFailureHandler.onAuthenticationFailure(request, response, e);
        }
        //3.登录请求不需要验证token
        doFilter(request, response, filterChain);
    }
    /**
     * 验证token
     */
    private void validateToken(HttpServletRequest request) throws AuthenticationException {
        //1.获取token
        String token = request.getHeader("Authorization");
        //如果请求头部没有获取到token,则从请求的参数中进行获取
        if (ObjectUtils.isEmpty(token)) {
            token = request.getParameter("Authorization");
        }
        if (ObjectUtils.isEmpty(token)) {
            throw new CustomerAuthenticationException("token不存在");
        }
        //2.redis进行校验
        String redisStr = stringRedisTemplate.opsForValue().get("token_" + token);
        if(ObjectUtils.isEmpty(redisStr)) {
            throw new CustomerAuthenticationException("token已过期");
        }
        //3.解析token
        Claims claims = null;
        try {
            claims = JwtUtils.parseJWT(token);
        } catch (Exception e) {
            throw new CustomerAuthenticationException("token解析失败");
        }
        //4.获取到用户信息
        String loginUserString = claims.getSubject();
        //把字符串转成loginUser对象
        LoginUser loginUser = JSON.parseObject(loginUserString, LoginUser.class);
        //创建身份验证对象
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
        //5.设置到Spring Security上下文
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
    }
}

(4)把jwt过滤器注册到springsecurity过滤器链中

放在UsernamePasswordAuthenticationFilter前面

@EnableWebSecurity
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig{
    //自定义jwt校验过滤器
    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        //配置关闭csrf机制
        http.csrf(csrf -> csrf.disable());
        //登陆失败处理器
        http.formLogin(configurer -> {
            configurer.failureHandler(loginFailureHandler);
        });
        http.sessionManagement(configurer ->
                // STATELESS(无状态): 表示应用程序是无状态的,不会创建会话。
                configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        );
        //请求拦截方式
        http.authorizeHttpRequests(auth -> auth
                .requestMatchers("/user/login").permitAll()
                .anyRequest().authenticated()
        );
        //!!!!!注册jwt过滤器!!!!!!!
        http.addFilterBefore(jwtAuthenticationTokenFilter,      
                             UsernamePasswordAuthenticationFilter.class);
        //异常处理器
        http.exceptionHandling(configurer -> {
            configurer.accessDeniedHandler(customerAccessDeniedHandler);
            configurer.authenticationEntryPoint(anonymousAuthenticationHandler);
        });
        return http.build();   //允许跨域
    }
    /**
     * 登录时需要调用AuthenticationManager.authenticate执行一次校验
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }
}

到此这篇关于SpringSecurity 认证实现的文章就介绍到这了,更多相关SpringSecurity 认证内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文