栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

学生程序设计能力提升平台源码分析(七)jwt+spring security+filter身份校验与权限控制分析

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

学生程序设计能力提升平台源码分析(七)jwt+spring security+filter身份校验与权限控制分析

前言

2021SC@SDUSC

概述

在之前的所有分析中,我们分析了项目的配置和架构情况,并且根据后端的MVC架构,在不同的层级,对spring boot框架本身以及注解的使用方法和内部原理进行了着重分析,希望能加深对spring boot框架机制的理解情况。

在后面的分析里,我们将把分析的重点放在项目所使用的其他技术上,比如jwt,spring security,rabbitmq,nginx等等,重点讲解其内部原理,附带项目配置和使用方法。

在本次的博客里,我们将重点先放在项目的身份校验与权限控制上,技术上使用了jwt+spring security+filter来实现。

基本配置

可参考:

Spring Security基本配置 - 仅此而已-远方 - 博客园

以及我项目组成员的具体介绍:

Spring Security+JWT实现身份认证与权限控制_alphahao的博客-CSDN博客

后者有在本项目的详细配置,不再赘述。

原理分析 身份校验

Spring Security的登录验证流程核心是过滤器链(Filter Chain)。

当一个请求到达时按照过滤器链的顺序依次进行处理,通过所有过滤器链的验证的请求才能访问接口。

同时,在验证的同时进行授权,方便之后的权限管理。

 

Spring Security的封装程度较高,我们使用时只需要调用其实现的方法就可以实现相关功能。

SpringSecurity提供了多种登录认证的方式,由多种Filter过滤器来实现,比如:

  • BasicAuthenticationFilter实现的是HttpBasic模式的登录认证
  • UsernamePasswordAuthenticationFilter实现用户名密码的登录认证
  • RememberMeAuthenticationFilter实现登录认证的“记住我”的功能
  • SmsCodeAuthenticationFilter实现短信验证码登录认证
  • SocialAuthenticationFilter实现社交媒体方式登录认证的处理
  • Oauth2AuthenticationProcessingFilter和Oauth2ClientAuthenticationProcessingFilter实现Oauth2的鉴权方式

以用户名密码的登录认证为例的流程如下:

 security config源码:

spring security的全局配置都在这里,在此方法中添加filter来控制身份校验的流程。

package cn.sdu.sdupta.config;


import cn.sdu.sdupta.filter.JwtAuthenticationFilter;
import cn.sdu.sdupta.filter.OptionsRequestFilter;
import cn.sdu.sdupta.handler.UsernameLoginSuccessHandler;
import cn.sdu.sdupta.handler.JwtRefreshSuccessHandler;
import cn.sdu.sdupta.service.JwtUserService;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlbasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

import java.util.Arrays;
import java.util.Collections;

@EnableWebSecurity(debug = false)
@EnableGlobalMethodSecurity(prePostEnabled = true)
class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //授权
        http.authorizeRequests()
                .antMatchers("/user/login", "/user/admin/login", "/user/register", "/user/logout", "/test/anonymous/**").permitAll()
                .anyRequest().authenticated()
                .and().csrf().disable()  //CSRF禁用,因为不使用session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).disable()  //禁用session
                .formLogin().disable() //禁用form登录
                .cors()
                .and()
                .addFilterAfter(new OptionsRequestFilter(), CorsFilter.class)
                //添加登录filter
                .apply(new JsonLoginConfig<>())
                .loginSuccessHandler(usernameLoginSuccessHandler())
                .and()
                //添加token的filter
                .apply(new JwtLoginConfig<>())
                .tokenValidSuccessHandler(jwtRefreshSuccessHandler())
                .permissiveRequestUrls("/logout")
                .and()
                //使用默认的logoutFilter
                .logout()
                .logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()); //logout成功后返回200
    }

    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    PasswordEncoder getPw() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    JwtAuthenticationFilter getJwtAuthenticationFilter() {
        return new JwtAuthenticationFilter();
    }


    @Override
    protected UserDetailsService userDetailsService() {
        return new JwtUserService();
    }

    @Bean("jwtUserService")
    protected JwtUserService jwtUserService() {
        return new JwtUserService();
    }

    @Bean
    protected UsernameLoginSuccessHandler usernameLoginSuccessHandler() {
        return new UsernameLoginSuccessHandler();
    }

    @Bean
    protected JwtRefreshSuccessHandler jwtRefreshSuccessHandler() {
        return new JwtRefreshSuccessHandler();
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration().applyPermitDefaultValues();
        configuration.addExposedHeader("Authorization");
        UrlbasedCorsConfigurationSource source = new UrlbasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

}

jwtconfig: Java web token 的配置,使用JwtAuthenticationFilter作为过滤器实现具体功能。

package cn.sdu.sdupta.config;

import cn.sdu.sdupta.filter.JwtAuthenticationFilter;
import cn.sdu.sdupta.handler.HttpStatusLoginFailureHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutFilter;

@Configuration
public class JwtLoginConfig, B extends HttpSecurityBuilder> extends AbstractHttpConfigurer {

    private final JwtAuthenticationFilter authFilter;

    public JwtLoginConfig() {
        this.authFilter = new JwtAuthenticationFilter();
    }

    @Override
    public void configure(B http) {
        authFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
        authFilter.setAuthenticationFailureHandler(new HttpStatusLoginFailureHandler());
        //将filter放到logoutFilter之前
        JwtAuthenticationFilter filter = postProcess(authFilter);
        http.addFilterBefore(filter, LogoutFilter.class);
    }

    //设置匿名用户可访问url
    public JwtLoginConfig permissiveRequestUrls(String... urls) {
        authFilter.setPermissiveUrl(urls);
        return this;
    }

    public JwtLoginConfig tokenValidSuccessHandler(AuthenticationSuccessHandler successHandler) {
        authFilter.setAuthenticationSuccessHandler(successHandler);
        return this;
    }
}
JsonLoginConfig:使用UsernamePasswordAuthenticationFilter完成用户名密码的校验
package cn.sdu.sdupta.config;

import cn.sdu.sdupta.filter.UsernamePasswordAuthenticationFilter;
import cn.sdu.sdupta.handler.HttpStatusLoginFailureHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy;

@Configuration
public class JsonLoginConfig, B extends HttpSecurityBuilder> extends AbstractHttpConfigurer {

    private final UsernamePasswordAuthenticationFilter authFilter;

    public JsonLoginConfig() {
        this.authFilter = new UsernamePasswordAuthenticationFilter();
    }

    @Override
    public void configure(B http) {
        //设置Filter使用的AuthenticationManager,这里取公共的即可
        authFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
        //设置失败的Handler
        authFilter.setAuthenticationFailureHandler(new HttpStatusLoginFailureHandler());
        //不将认证后的context放入session
        authFilter.setSessionAuthenticationStrategy(new NullAuthenticatedSessionStrategy());

        UsernamePasswordAuthenticationFilter filter = postProcess(authFilter);
        //指定Filter的位置
        http.addFilterAfter(filter, LogoutFilter.class);
    }

    //设置成功的Handler,这个handler定义成Bean,所以从外面set进来
    public JsonLoginConfig loginSuccessHandler(AuthenticationSuccessHandler authSuccessHandler) {
        authFilter.setAuthenticationSuccessHandler(authSuccessHandler);
        return this;
    }

}

UsernamePasswordAuthenticationFilter:

package cn.sdu.sdupta.filter;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AndRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;

@WebFilter
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    public UsernamePasswordAuthenticationFilter() {
        super(new AntPathRequestMatcher("/user/login", "POST"));
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        String username, password;
        username = request.getParameter("username");
        password = request.getParameter("password");

        if (username == null) {
            username = "";
        }
        if (password == null) {
            password = "";
        }
        //封装到token中提交
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
        return getAuthenticationManager().authenticate(authRequest);
    }
}
OptionsRequestFilter: 拦截options请求
package cn.sdu.sdupta.filter;

import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class OptionsRequestFilter extends oncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        if (request.getMethod().equals("OPTIONS")) {
            response.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,HEAD");
            response.setHeader("Access-Control-Allow-Headers", response.getHeader("Access-Control-Request-Headers"));
            return;
        }
        filterChain.doFilter(request, response);
    }

}
JwtAuthenticationFilter:对token进行校验,同时完成授权
package cn.sdu.sdupta.filter;

import cn.sdu.sdupta.domain.PtaUser;
import cn.sdu.sdupta.entity.ResultEntity;
import cn.sdu.sdupta.entity.StatusCode;
import cn.sdu.sdupta.util.JwtUtil;
import cn.sdu.sdupta.util.RedisUtil;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class JwtAuthenticationFilter extends oncePerRequestFilter {
    private final RequestHeaderRequestMatcher requiresAuthenticationRequestMatcher;
    private List permissiveRequestMatchers;
    private AuthenticationManager authenticationManager;

    private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
    private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();

    @Autowired
    RedisUtil redisUtil;

    public JwtAuthenticationFilter() {
        //拦截header中带Authorization的请求
        this.requiresAuthenticationRequestMatcher = new RequestHeaderRequestMatcher("Authorization");
    }

    protected String getJwtToken(HttpServletRequest request) {
        return request.getHeader(JwtUtil.TOKEN_HEADER);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (!requiresAuthentication(request)) {
            chain.doFilter(request, response);
            return;
        }
        String tokenHeader = getJwtToken(request);
        if (tokenHeader == null || !tokenHeader.startsWith(JwtUtil.TOKEN_PREFIX)) {
            chain.doFilter(request, response);
            return;
        }
        try {
            if (!JwtUtil.isExpiration(tokenHeader.replace(JwtUtil.TOKEN_PREFIX, ""))) {
                UsernamePasswordAuthenticationToken authentication = getAuthentication(tokenHeader);
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (ExpiredJwtException e) {
            response.setCharacterEncoding("UTF-8");
            response.setHeader("Content-Type", "text/html;charset=UTF-8");
            response.getWriter().print(ResultEntity.error(StatusCode.USER_TOKEN_OVERDUE));
            return;
        } catch (JwtException e) {
            response.setCharacterEncoding("UTF-8");
            response.setHeader("Content-Type", "text/html;charset=UTF-8");
            response.getWriter().print(ResultEntity.error(StatusCode.USER_TOKEN_ERROR));
            return;
        } catch (Exception e) {
            response.setCharacterEncoding("UTF-8");
            response.setHeader("Content-Type", "text/html;charset=UTF-8");
            response.getWriter().print(ResultEntity.error(StatusCode.COMMON_FAIL));
            return;
        }
        chain.doFilter(request, response);
    }

    //获取用户信息
    private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) {
        String token = tokenHeader.replace(JwtUtil.TOKEN_PREFIX, "");
        String account = JwtUtil.getUsername(token);
        Integer userId = JwtUtil.getUserId(token);
        if (account == null || userId == null) {
            return null;
        }
        // 获得权限 添加到权限上去
        String roles = JwtUtil.getUserRole(token);
        PtaUser ptaUser = new PtaUser(account, "[PROTECTED]", AuthorityUtils.commaSeparatedStringToAuthorityList(roles));
        ptaUser.setUserId(userId);
        return new UsernamePasswordAuthenticationToken(ptaUser, null, AuthorityUtils.commaSeparatedStringToAuthorityList(roles));
    }

    protected boolean requiresAuthentication(HttpServletRequest request) {
        return requiresAuthenticationRequestMatcher.matches(request);
    }

    protected AuthenticationManager getAuthenticationManager() {
        return authenticationManager;
    }

    public void setAuthenticationManager(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }


    protected boolean permissiveRequest(HttpServletRequest request) {
        if (permissiveRequestMatchers == null)
            return false;
        for (RequestMatcher permissiveMatcher : permissiveRequestMatchers) {
            if (permissiveMatcher.matches(request))
                return true;
        }
        return false;
    }

    public void setPermissiveUrl(String... urls) {
        if (permissiveRequestMatchers == null)
            permissiveRequestMatchers = new ArrayList<>();
        for (String url : urls)
            permissiveRequestMatchers.add(new AntPathRequestMatcher(url));
    }

    public void setAuthenticationSuccessHandler(
            AuthenticationSuccessHandler successHandler) {
        Assert.notNull(successHandler, "successHandler cannot be null");
        this.successHandler = successHandler;
    }

    public void setAuthenticationFailureHandler(
            AuthenticationFailureHandler failureHandler) {
        Assert.notNull(failureHandler, "failureHandler cannot be null");
        this.failureHandler = failureHandler;
    }

    protected AuthenticationSuccessHandler getSuccessHandler() {
        return successHandler;
    }

    protected AuthenticationFailureHandler getFailureHandler() {
        return failureHandler;
    }
}
权限控制

不同控制方式可参考:

Spring Security 中的四种权限控制方式_江南一点雨的专栏-CSDN博客

我们将在下一次博客中详细介绍。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/584604.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号