SpringSecurity集成JWT认证框架

1. JWT

        JWT简称JSON Web Token,也就是通过JSON形式作为Web应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输。在数据传输过程中还可以完成数据加密、签名等相关处理。

        JWT的由,header(标头),payload(有效载荷),singnature(签名),三个部分组成的一个Stirng类型的字符串。可以通过对字符串的编码和解密,完成WEB端的登录认证和授权。

        JWT的工作流程如下图所示。

SpringSecurity集成JWT认证框架_第1张图片

        JWT实现的核心代码有两个,一个是实现拦截器接口,一个是将拦截器注入Spring容器中运行。

        以下是实现拦截器接口,方法是重写前置拦截器,从请求头中获取token的数据进行判断,数据无误可放行,数据不对进行拦截。

public class JWTInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //获取请求头中令牌
        String token = request.getHeader("token");
        try {
            JWTUtils.verify(token);//验证令牌
            return true;//放行请求
        }catch (Exception e){
            e.printStackTrace();
        }
        return false;
    }
}

将拦截器注入Spring容器中,下面需要配置将JWT拦截器进行添加,然后添加下面需要拦截的路径和不需要拦截的路劲。

public class InterceptorConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new JWTInterceptor())
                .addPathPatterns("/user/test")         //其他接口token验证
                .excludePathPatterns("/user/login");  //排除拦截路径
    }
}

2. SpringSecurity

        SpringSecurity是一个安全框架,它有认证授权这两个核心功能。

        用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。通俗点说就是系统认为用户是否能登录。

        用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。通俗点讲就是系统判断用户是否有权限去做某些事情。

        SpringSecurity有三个重要个实现类和一个接口,我们可以通过重写实现类里面的方法对SpringSecurity进行的配置。他们的调用流程如下。

SpringSecurity集成JWT认证框架_第2张图片

        第一个类名是UsernamePasswordAuthenticationFilter的过滤器,这个可以通过这个这类获取登录页面提交的账号和密码进行处理,并且可以配置方法的成功和失败。

public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {

    public TokenLoginFilter() {
        this.setPostOnly(false);
        this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login","POST"));
    }
    
	//1 获取表单提交用户名和密码
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException {
        //获取表单提交数据
        User user = new ObjectMapper().readValue(request.getInputStream(), User.class);
        return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword(),
                    new ArrayList<>()));
    }
    
	//2 认证成功调用的方法
    @Override
    protected void successfulAuthentication(HttpServletRequest request, 
                                            HttpServletResponse response, FilterChain chain, Authentication authResult)
            throws IOException, ServletException {
        //认证成功,得到认证成功之后用户信息
        //设置token或者设置cookie都在这
    }

    //3 认证失败调用的方法
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed)
            throws IOException, ServletException {
    }
    
}

        第二个接口是UserDetailsServie的接口,这是接口是SrpingSecurity的用户数据认证接口,需要重写里面的loadUserByUsername方法,在数据库中找到相应的用户数据,复制到UserDetails的类型中返回,就可以对账号就行认证。

public class UserDetailsServiceImpl implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //根据用户名查询数据
        //将数据库的数据复制到UserDetails的类型中
        UserDetails userDetails = new UserDetails();
        return userDetails;
    }

第三个类是BasicAuthenticationFilter的实现类,重写doFilterInternal方法,这个就是配置权限的方法了,除了登录方法排除在外,其他方法都会进入到这个方法加载权限。然后SpringSecurity就会根据这个权限来判断请求的方法是否可以继续访问。

public class TokenAuthFilter extends BasicAuthenticationFilter {
     @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        //获取当前认证成功用户权限信息
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken();
        //判断如果有权限信息,放到权限上下文中
        SecurityContextHolder.getContext().setAuthentication(authRequest);
        
        chain.doFilter(request,response);
    }
}

        第四个配置类是WebSecurityConfigurerAdapter对SpringSecurity进行注入到Spring的容器中运行,需要重写configure的方法,配置数据。

public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {
     @Override
    protected void configure(HttpSecurity http) throws Exception {
        //配置特定权限
        http.authorizeRequests()
                .antMatchers("/admin/acl/user/**").hasAuthority("user.list")
             //没有权限访问   
            .and().exceptionHandling()
                .authenticationEntryPoint(new UnauthEntryPoint())
                //跨域问题
            .and().csrf().disable()
               //退出路径 
            .authorizeRequests()
                .anyRequest().authenticated()
                .and().logout().logoutUrl("/admin/acl/index/logout")
                .addLogoutHandler(new TokenLogoutHandler(tokenManager,redisTemplate))
                //添加自定义过滤器
            .and().addFilter(new TokenLoginFilter(authenticationManager(), tokenManager, redisTemplate))
                .addFilter(new TokenAuthFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic();
    }
}

3.集成JWT

        SpringSecurity+jwt的集成,登录的整个流程。

SpringSecurity集成JWT认证框架_第3张图片

        集成jwt就是在SpringSecurity架构认证成功之后去添加token返回给前端,这个认证成功的方法就是successfulAuthentication,在这里去添加token返回。

    protected void successfulAuthentication(HttpServletRequest request, 
                                            HttpServletResponse response, FilterChain chain, Authentication authResult)
            throws IOException, ServletException {
        //认证成功,得到认证成功之后用户信息
        SecurityUser user = (SecurityUser)authResult.getPrincipal();
        //根据用户名生成token
        String token = tokenManager.createToken(user.getCurrentUserInfo().getUsername());
        //把用户名称和用户权限列表放到redis
        redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(),user.getPermissionValueList());
        //返回token
        ResponseUtil.out(response, R.ok().data("token",token));
    }

        返回给前端后,前端会将token设置在请求头,之后在每次请求都会发送回来。我们可以在授权前对token进行校验,看是否授权。

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        //获取当前认证成功用户权限信息
        UsernamePasswordAuthenticationToken authRequest = getAuthentication(request);
        //判断如果有权限信息,放到权限上下文中
        if(authRequest != null) {
            SecurityContextHolder.getContext().setAuthentication(authRequest);
        }
        chain.doFilter(request,response);
    }

private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        //从header获取token
        String token = request.getHeader("token");
        if(token != null) {
            //从token获取用户名
            String username = tokenManager.getUserInfoFromToken(token);
            //从redis获取对应权限列表
            List permissionValueList = (List)redisTemplate.opsForValue().get(username);
            Collection authority = new ArrayList<>();
            for(String permissionValue : permissionValueList) {
                SimpleGrantedAuthority auth = new SimpleGrantedAuthority(permissionValue);
                authority.add(auth);
            }
            return new UsernamePasswordAuthenticationToken(username,token,authority);
        }
        return null;
    }

4. 附件

  1. 资源文件:百度云
  2. 不清楚的可以看视频

bilibili htmicon-default.png?t=M85Bhttps://player.bilibili.com/player.html?aid=202502517&bvid=BV15a411A7kP&cid=256421944&page=1

你可能感兴趣的:(servlet,java,spring)