springboot集成springsecurity+jwt

本菜鸟写的第一篇博客,可能有很多的不足请见谅;
文章内使用的源码已经放到了github上,请放心食用:

SpringSecurity+Jwt 的 Demo
本文代码只截取重要部分,其他代码请自行到github上demo处进行查看

一、SpringSecurity简单介绍

Spring Security 基于Spring 框架,提供了一套web应用安全性的完整解决方案。一般来说,它的安全性包括两部分:
用户认证:包括 HTTP 基本认证、HTTP 表单验证、HTTP 摘要认证、OpenID 和 LDAP 等。
用户授权:基于角色的访问控制和访问控制列表(Access Control List,ACL),可以对应用中的领域对象进行细粒度的控制。

SpringSecurity的核心组件的简单介绍: Userdetails 、UserDetailsService、Authentication、AuthenticationProvider、AuthenticationManager

UserDetails:用来存储获取到的用户信息的实体
有以下方法:
getAuthorites:获取用户权限(权限或角色)
getPassword: 获取密码
getUserName: 获取用户名
isAccountNonExpired: 账户是否过期
isAccountNonLocked: 账户是否被锁定
isCredentialsNonExpired: 密码是否过期
isEnabled: 账户是否可用

UserDetailsService:获取数据组装成一个userDetails
有以下方法:
loadUserByUsername :此方法中可以通过数据库或其他途径获取到用户信息,然后封装成一个userDetails

Authentication:简单的理解为一个用来保存 用户名和密码或者token 信息的实体
比如本文中用到的:UsernamePasswordAuthenticationToken和JwtAuthenticationToken,它们两个继承了AbstractAuthenticationToken,但AbstractAuthenticationToken实现了Authentication;本文中以上的两个专门用来保存接收的用户名和密码或token信息。
有以下方法:
getAuthorities: 获取用户权限(权限或角色)
getCredentials: 获取到的是密码等信息
getDetails: 获取用户的额外信息
getPrincipal: 获取到的是用户名
isAuthenticated: 获取当前 Authentication 是否已认证
setAuthenticated: 设置当前 Authentication 是否已认证

AuthenticationProvider:负责真正校验
一个provider相当于是一个认证方式的实现,比如登录时的认证,就有一个DaoAuthenticationProvider,验证jwt的token是否有效等,就有一个自定义的JwtAuthenticationProvider。

AuthenticationManager:用户认证的管理类,所有的认证请求(比如login)都会通过提交一个token给AuthenticationManager的authenticate()方法来实现。当然事情肯定不是它来做,具体校验动作会由AuthenticationManager将请求转发给具体的provider实现类来做。根据实现反馈的结果再调用具体的Handler来给用户以反馈

SpringSecurity的认证流程图:

在这里插入图片描述

二、SpringSecurity的具体实现

此demo分为两部分,一部分是登录的认证流程,另一部分是带有token的认证流程

本文代码只截取重要部分,其他代码请自行到git上demo处进行查看

实体类和数据库表:

用户表: password的值记得 passwordEncoder.encode进行加密 ,账号222和333是没有加密
@Data
@TableName(“user”)
public class SysUser {
private int id;
private String userName;
private String password;
}
在这里插入图片描述

角色表:
@Data
@TableName(“role”)
public class Role {
private int id;
private String userName;
private String roleName;
}
在这里插入图片描述

权限表:
@Data
@TableName(“permission”)
public class Permission {
private int id;
private String roleName;
//权限名称
private String permissionName;
}
在这里插入图片描述
springsecurity+jwt所需依赖

<!--SpringSecurity的依赖包,不用写版本号,springboot2.0环境下默认使用5.0版本-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <!-- jwt -->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.2.0</version>
        </dependency>

登录的认证流程

(1)先自定义CustomUserDetailsService实现UserDetailsService接口,在loadUserByUsername方法中组装userDetails ,此类去github上查看完整。

@Service
public class CustomUserDetailsService implements UserDetailsService {
	@Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{

        Collection<GrantedAuthority> authorities = new ArrayList<>();

        SysUser sysUser = sysUserService.getByUserName(username);

        //添加角色
        List<Role> roleList = roleService.getByUserName(username);
        ArrayList<String> list = new ArrayList<>();
        for (Role role:roleList){
            list.add(role.getRoleName());
        }
        String[] roleName = list.toArray(new String[list.size()]) ;

        //添加权限
        for(Role role:roleList){
            List<Permission> permissionList = permissionService.getByRoleName(role.getRoleName());
            for (Permission permission:permissionList){
                authorities.add(new SimpleGrantedAuthority(permission.getPermissionName()));
            }
        }

        //坑  角色验证和权限认证,只能选择其中之一去效验,框架源码里面最后还是返回authorities
        return User.builder().username(sysUser.getUserName()).password(sysUser.getPassword())
                .roles(roleName)
//                .authorities(authorities)
                .build();
    }
}

此处的User是springsecurity中独有的User(实现UserDetails),本文对应的数据库User表的实体类为SysUser;此处有个坑 .roles()和.authorities()只能选择一个,如果同时两个一起应用,只有一个生效,只能注释掉其他一个,然后看了下两个方法的实现,最终都是把角色或者权限放入authorities,所以只有其中一个生效。(如有错误请指出,接触不深)

接着在 CustomUserDetailsService 放入以下代码(jwt的生成和存放盐):

/**
 *  这里主要是存放盐,后面需要得到这个方法里面的盐
 */
public UserDetails getUserLoginInfo(String username){
    //盐可以从数据库或者缓存中取出jwt token生成时用的salt,这里直接自定义
    String salt ="123456ef";
    UserDetails user = loadUserByUsername(username);
    return User.builder()
            .username(user.getUsername())
            .password(salt)
            .authorities(user.getAuthorities())
            .build();
}

/**
 *  生成jwt  ,jwt前面我加了前缀,用于区别
 */
public String saveUserLoginInfo(UserDetails user) throws UnsupportedEncodingException {
    //BCrypt.gensalt();  正式开发时可以调用该方法实时生成加密的salt
    String salt ="123456ef";
    //然后将salt保存到数据库或者缓存中
    Algorithm algorithm = Algorithm.HMAC256(salt);
    //设置1小时后过期
    Date date = new Date(System.currentTimeMillis()+3600*1000);
    //在jwt前面匹配自己的标识
    return "lza-"+JWT.create()
            .withSubject(user.getUsername())
            .withExpiresAt(date)
            .withIssuedAt(new Date())
            .sign(algorithm);
}

(2)登录请求的过滤器filter,用于json发送登录信息(spring security默认支持form方式登录),继承AbstractAuthenticationProcessingFilter,实现两个步骤,一个是获取RequestMatcher指名拦截的Request类型,另一个是获取从json body中提取的username和password,接着提交给manager处理:

public class MyUsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    public MyUsernamePasswordAuthenticationFilter(){
        //拦截url为“/login”的post请求
        super(new AntPathRequestMatcher("/login","POST"));
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException {
        //从json中获取username和password
        String body = StreamUtils.copyToString(request.getInputStream(), Charset.forName("UTF-8"));
        String username = null;
        String password = null;
        if(StringUtils.hasText(body)){
            JSONObject json = JSON.parseObject(body);
            username = json.getString("username");
            password = json.getString("password");
        }
        if(username==null){
            username = "";
        }
        if(password==null){
            password = "";
        }
        //去掉前后的空格
        username = username.trim();
        //封装到token中提交
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username,password);
		//提交给manager管理器分配给具体provider实现类处理
        return this.getAuthenticationManager().authenticate(authRequest);
    }
}

(3)对于登录的provider,spring security已经提供了一个默认实现DaoAuthenticationProvider给我们可以直接使用,这里我们自定义继承DaoAuthenticationProvider做我们自己的登录处理:

public class JsonLoginProvider extends DaoAuthenticationProvider {

    private static final Logger logger = LoggerFactory.getLogger(JsonLoginProvider.class);
    private CustomUserDetailsService userDetailsService;
    @Autowired
    private SysUserService sysUserService;

/**
 *  这里必须指定密码加密的加密方式
 */
    public JsonLoginProvider(CustomUserDetailsService userDetailsService, BCryptPasswordEncoder passwordEncoder){
        this.setUserDetailsService(userDetailsService);
        this.setPasswordEncoder(passwordEncoder);
        this.userDetailsService = userDetailsService;
    }

/**
 *  这里实现了认证流程,参数authentication里面是登录filter传入的token
 */
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = (String) authentication.getPrincipal();
        String password = (String) authentication.getCredentials();
        SysUser sysUser = sysUserService.getByUserName(username);

        if(sysUser == null){
            logger.info("用户不存在");
            throw new UsernameNotFoundException("用户不存在");
        }
		//明文的密码和加密后的密码进行比较
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        if(!encoder.matches(password,sysUser.getPassword())){
            logger.info("密码错误,请重试");
            throw new BadCredentialsException("密码错误,请重试");
        }
        return super.authenticate(authentication);
    }
}

(4)把以上组合到一起就可以就形成登录的流程:

public class JsonLoginConfigurer<T extends JsonLoginConfigurer<T, B>, B extends HttpSecurityBuilder<B>> extends AbstractHttpConfigurer<T, B> {

    private MyUsernamePasswordAuthenticationFilter authFilter;

    public JsonLoginConfigurer(){
        this.authFilter = new MyUsernamePasswordAuthenticationFilter();
    }

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

        MyUsernamePasswordAuthenticationFilter filter = postProcess(authFilter);
        //制定filter的位置
        http.addFilterAfter(filter, LogoutFilter.class);
    }

    //设置成功的Handler
    public JsonLoginConfigurer<T,B> loginSuccessHandler(AuthenticationSuccessHandler authSuccessHandler){
        authFilter.setAuthenticationSuccessHandler(authSuccessHandler);
        return this;
    }
}

这里的filter的位置,请查看下面知识补充,小伙伴们就知道为什么把登录的filter放到LogoutFilter.class的后面。

(5)对于provider,只有成功与失败两种结果,我们只需要实现两个Handler接口即可;

成功Handler接口:登录成功后需要把token返回给前端:

public class JsonLoginSuccessHandler implements AuthenticationSuccessHandler {

    private static final Logger logger = LoggerFactory.getLogger(JsonLoginFailHandler.class);
    private CustomUserDetailsService userDetailsServicel;

    public JsonLoginSuccessHandler(CustomUserDetailsService userDetailsService){
        this.userDetailsServicel = userDetailsService;
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        String token = userDetailsServicel.saveUserLoginInfo((UserDetails)authentication.getPrincipal());
        response.setHeader("Authorization",token);
        logger.info("验证成功");
    }
}

失败Handler接口:失败后需要把失败的信息返回给前端,失败的信息从AuthenticationException里面获取,AuthenticationException是springsecurity里面所有异常子类的父类:

public class JsonLoginFailHandler implements AuthenticationFailureHandler {

    private static final Logger logger = LoggerFactory.getLogger(JsonLoginFailHandler.class);

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        logger.info("通过失败");
        response.setCharacterEncoding("utf-8");
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        LinkedHashMap<Object,Object> map = new LinkedHashMap<>();
        map.put("success",true);
        map.put("errorCode", HttpStatus.UNAUTHORIZED.value());
		//这里获取异常的信息
        map.put("msg",e.getMessage());
        map.put("data","");
        ObjectMapper objectMapper = new ObjectMapper();
        String resBody = objectMapper.writeValueAsString(map);
        PrintWriter printWriter = response.getWriter();
        printWriter.print(resBody);
        printWriter.flush();
        printWriter.close();
    }

}

这样登录的流程就配置好了,后续我们需要把对应的配置到WebSecurityConfig。

带Token请求校验流程

(1)自定义一个JwtAuthenticationToken 继承 AbstractAuthenticationToken,用于接收请求携带的token。

public class JwtAuthenticationToken extends AbstractAuthenticationToken {

    private UserDetails principal;
    private String credentials;
    private DecodedJWT token;

    public JwtAuthenticationToken(DecodedJWT token) {
        super(Collections.emptyList());
        this.token = token;
    }

    public JwtAuthenticationToken(UserDetails principal, DecodedJWT token, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        this.token = token;
    }

    @Override
    public void setDetails(Object details) {
        super.setDetails(details);
        this.setAuthenticated(true);
    }

    @Override
    public Object getCredentials() {
        return credentials;
    }

    @Override
    public Object getPrincipal() {
        return principal;
    }

    public DecodedJWT getToken() {
        return token;
    }
}

(2)登录后的请求,需要携带jwt token,需要把这些请求拦截,然后提交给manager处理,此类去git上查看完整版,成功或者失败的返回都用登录流程的那两个handler接口实现类:

public class JwtAuthenticationFilter extends OncePerRequestFilter {

	protected String getJwtToken(HttpServletRequest request){
	    String authInfo = request.getHeader("Authorization");
	    String prefix = "lza-";
	    //移除开头匹配的lza-,比如得到lza-xxxxxsas,使用方法后得到xxxxxsas
	    return StringUtils.removeStart(authInfo,prefix);
	}
	
	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
	    //这里选择没有token的请求也放过,因为后面操作时需要权限,那时候也会被拦截掉,
	    //如果不想放过没有token的请求,这里直接抛出个异常说明没有携带token即可
	    if(!requiresAuthentication(request,response)){
	        filterChain.doFilter(request,response);
	        return;
	    }
	
	    Authentication authResult = null;
	    AuthenticationException failed = null;
	
	    try{
	        //从头中获取token并封装后提交给AuthenticationManager
	        String token = getJwtToken(request);
	        if(StringUtils.isNotBlank(token)){
	            JwtAuthenticationToken authToken = new JwtAuthenticationToken(JWT.decode(token));
	            //提交到manager管理器,分配给具体provider处理,返回结果
	            authResult = this.getAuthenticationManager().authenticate(authToken);
	        }else{
	            //如果token长度为0
	            failed = new InsufficientAuthenticationException("Token为空");
	        }
	    }catch (JWTDecodeException e){
	        logger.error("Token格式错误", e);
	        failed = new InsufficientAuthenticationException("Token格式错误", failed);
	    }catch (InternalAuthenticationServiceException e){
	        logger.error(
	                "尝试对用户进行身份验证时发生内部错误",
	                failed);
	        failed = e;
	    }catch (AuthenticationException e){
	        failed = e;
	    }
	
	    //token认证成功
	    if(authResult!=null){
	        successfulAuthentication(request,response,filterChain,authResult);
	    }else if(!permissiveRequest(request)){
	        //token认证失败,并且这个request不再例外列表里,才会返回错误
	        unsuccessfulAuthentication(request,response,failed);
	        return;
	    }
	    filterChain.doFilter(request,response);
	}
}

(3)像登录流程一样,filter拦截后需要有provider处理

public class JwtAuthenticationProvider implements AuthenticationProvider {

    private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationProvider.class);

    private CustomUserDetailsService userDetailsService;

    public JwtAuthenticationProvider(CustomUserDetailsService userDetailsService){
        this.userDetailsService = userDetailsService;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    
        DecodedJWT jwt = ((JwtAuthenticationToken)authentication).getToken();
        if(jwt.getExpiresAt().before(Calendar.getInstance().getTime())){
            logger.info("Token 过期");
            throw new NonceExpiredException("Token 过期");
        }
        
        String username = jwt.getSubject();
        UserDetails user = userDetailsService.getUserLoginInfo(username);
        if(user==null||user.getPassword()==null){
            logger.info("Token加密的salt 过期");
            throw new NonceExpiredException("Token加密的salt 过期");
        }
        
        //验证token
        String encryptSalt = user.getPassword();
        try{
            Algorithm algorithm = Algorithm.HMAC256(encryptSalt);
            JWTVerifier verifier = JWT.require(algorithm)
                    .withSubject(username)
                    .build();
            verifier.verify(jwt.getToken());
        } catch (UnsupportedEncodingException e) {
            logger.info("Token认证失败");
            throw new BadCredentialsException("Token认证失败",e);
        } catch (JWTVerificationException e){
            logger.info("Token认证失败");
            throw new BadCredentialsException("Token认证失败",e);
        }
        //成功后返回认证信息,filter会将认证信息放入SecurityContext
        JwtAuthenticationToken token = new JwtAuthenticationToken(user,jwt,user.getAuthorities());
        return token;
    }

    /**
     * supports支持很多子类,比如JwtAuthenticationToken
     */
    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.isAssignableFrom(JwtAuthenticationToken.class);
    }
}

(4)把以上流程整合在一起,带有token的请求处理流程就配置好了:

public class JwtLoginConfigurer<T extends JwtLoginConfigurer<T, B>, B extends HttpSecurityBuilder<B>> extends AbstractHttpConfigurer<T, B> {

    private JwtAuthenticationFilter authFilter;

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

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

    //设置匿名用户可访问的url
    public JwtLoginConfigurer<T, B> permissiveRequestUrls(String ... urls){
        authFilter.setPermissiveUrl(urls);
        return this;
    }

    public JwtLoginConfigurer<T, B> tokenValidSuccessHandler(AuthenticationSuccessHandler successHandler){
        authFilter.setSuccessHandler(successHandler);
        return this;
    }
}

配置集成

把登录流程和带有token请求的认证流程整合到配置类中,@EnableWebSecurity开启security服务,
@EnableGlobalMethodSecurity开启全局security注解,@ComponentScan这里使用的原因是我启动时总会报无法注入service层和dao层的类,因此我这边加入个扫描注解。(启动类在最外面,按理来说是可以扫描到的,也不知道是不是启动注入到spring的顺序问题还是自己配置的原因)。
(注意:该类去github上看完整版,只复制了主要代码)

@EnableWebSecurity
@ComponentScan(value = {"com.service","com.dao"})
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
  @Autowired
    private CustomUserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    	//添加使用的provider
		auth.authenticationProvider(jsonLoginProvider())
		.authenticationProvider(jwtAuthenticationProvider());
    }

    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                //静态资源访问无需认证
//                .antMatchers("/image/**").permitAll()
                //admin开头的请求,需要admin权限
//                .antMatchers("/admin/**").hasAnyRole("ADMIN")
                //需要登陆才能访问的url
//                .antMatchers("/article/**").hasRole("USER")
                //默认其它的请求都需要认证,这里一定要添加
                .anyRequest().authenticated()
                .and()
                .exceptionHandling()
                //权限校验失败走这个类里面的处理方法
                .accessDeniedHandler(new SimpleAccessDeniedHandler())
                .and()
                //csrf禁用,因为不使用session
                .csrf().disable()
                //禁用session
                .sessionManagement().disable()
                //禁用form登陆
                .formLogin().disable()
                //支持跨域
                .cors()
                .and()
                //支持header设置,支持跨域和ajax请求
                .headers().addHeaderWriter(new StaticHeadersWriter(Arrays.asList(
                        new Header("Access-control-Allow-Origin","*"),
                        new Header("Access-Control-Expose-Headers","Authorization"))))
                .and()
                //拦截OPTIONS请求,直接返回header
                .addFilterAfter(new OptionsRequestFilter(), CorsFilter.class)
                //添加登陆filter
                .apply(new JsonLoginConfigurer<>()).loginSuccessHandler(jsonLoginSuccessHandler())
                .and()
                //添加token的filter
                .apply(new JwtLoginConfigurer<>()).tokenValidSuccessHandler(jsonLoginSuccessHandler())
                .and()
                //使用默认的logoutFilter
                .logout()
                    //默认时"/logout"
                    .logoutUrl("/logout")
                    .addLogoutHandler(tokenClearLogoutHandler())
                    //logout成功后返回200
                    .logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler())
                .and()
                .sessionManagement().disable();
    }
}

权限校验失败的话是返回403的失误,这边对于权限检验失败结果进行封装(也就是美化一下返回给前端的结果集),自定义SimpleAccessDeniedHandler,实现AccessDeniedHandler接口,同时需要配置到WebSecurityConfig(上面WebSecurityConfig已配置进去)

public class SimpleAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
        response.setCharacterEncoding("utf-8");
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        LinkedHashMap<Object,Object> map = new LinkedHashMap<>();
        map.put("success",true);
        map.put("errorCode", HttpStatus.FORBIDDEN.value());
        map.put("msg","权限不足");
        map.put("data","");
        ObjectMapper objectMapper = new ObjectMapper();
        String resBody = objectMapper.writeValueAsString(map);
        PrintWriter printWriter = response.getWriter();
        printWriter.print(resBody);
        printWriter.flush();
        printWriter.close();
    }
}

三、额外知识补充:

一、springsecurity的filter顺序

SpringSecurity 采用的是责任链的设计模式,它有一条很长的过滤器链。现在对这条过滤器链的各个进行说明:

1.WebAsyncManagerIntegrationFilter: 将Security上下文与Spring Web 中用于处理异步请求映射的WebAsyncManager进行集成。

2.SecurityContextPersistenceFilter:在每次请求处理之前将该请求相关的安全上下文信息加载到SecurityContextHolder 中,然后在该次请求处理完成之后,将 SecurityContextHolder 中关于这次请求的信息存储到一个“仓储”中,然后将 SecurityContextHolder 中的信息清除,例如在Session中维护一个用户的安全信息就是这个过滤器处理的。

3.HeaderWriterFilter:用于将头信息加入响应中。

4.CsrfFilter:用于处理跨站请求伪造。

5.LogoutFilter:用于处理退出登录。

6.UsernamePasswordAuthenticationFilter:用于处理基于表单的登录请求,从表单中获取用户名和密码。默认情况下处理来自 /login 的请求。从表单中获取用户名和密码时,默认使用的表单 name 值为 username 和 password,这两个值可以通过设置这个过滤器的usernameParameter 和 passwordParameter 两个参数的值进行修改。

7.DefaultLoginPageGeneratingFilter:如果没有配置登录页面,那系统初始化时就会配置这个过滤器,并且用于在需要进行登录时生成一个登录表单页面。

8.BasicAuthenticationFilter:检测和处理 http basic 认证。

9.RequestCacheAwareFilter:用来处理请求的缓存。

10.SecurityContextHolderAwareRequestFilter:主要是包装请求对象request。

11.AnonymousAuthenticationFilter:检测 SecurityContextHolder 中是否存在 Authentication 对象,如果不存在为其提供一个匿名 Authentication。

12.SessionManagementFilter:管理 session 的过滤器

13.ExceptionTranslationFilter:处理 AccessDeniedException 和 AuthenticationException 异常。

14.FilterSecurityInterceptor:可以看做过滤器链的出口。

15.RememberMeAuthenticationFilter:当用户没有登录而直接访问资源时, 从 cookie 里找出用户的信息, 如果 Spring Security 能够识别出用户提供的remember me cookie, 用户将不必填写用户名和密码, 而是直接登录进入系统,该过滤器默认不开启。

所以上述的登录的filter放到logoutFilter的后面,带有token的filter放到logoutFilter的前面

二、权限校验

身份检验完之后就会进行权限校验,userDetails会从CustomUserDetailsService的loadUserByUsername中得到,里面包含权限信息,然后与接口的权限进行比较,自己可以去自定义一个权限校验的类,具体的可以自行去搜索,我这边没有去实现。下图的是比对角色,比对权限是 @PreAuthorize(“hasAuthority(‘user:select’)”)
在这里插入图片描述
三、filter对应provider

如果provider很多的话,怎么分清楚filter对应哪个provider,这时候需要看provider接受的是哪个token,比如处理带有token请求的provider;UsernamePasswordAuthenticationToken对应的是DaoAuthenticationProvider(springsecurity已有不需要我们去实现,但可以自行重新定义)
filter:
在这里插入图片描述
provider:
在这里插入图片描述
四、userDetails是需要进行保存的,UserDetails与缓存的交互是通过UserCache接口来实现的。

以上是本篇的全部内容,只展示了主要代码,可以自行去github上查看完整代码。

以上大部分内容是从此链接拷贝学习并添加自己的代码和形成自己的理解:https://www.jianshu.com/p/d5ce890c67f7
如有侵权请告知,谢谢!
如有什么地方不足或错误请告知,谢谢!

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值