Gateway 整合 Spring Security鉴权

Posted 在代码中沉醉

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Gateway 整合 Spring Security鉴权相关的知识,希望对你有一定的参考价值。

目录

Spring-Security

Spring-Webflux

注意

编码

项目环境版本

gradle 依赖

Spring-Security配置

1. Security核心配置

2.用户认证

3.1 自定义登录成功Handler

3.2 自定义登录失败Handler

3.3 自定义未认证Handler

3.4 自定义鉴权失败Handler

4.自定义JWT Token认证管理

5.自定义鉴权管理

2.springsecruity密码判断

3.流程

3.尚硅谷springsecurity

3.3两个重要接口 

3.3.1认证

 3.3.2自定义登入

 403设计​编辑

 3.5用户注销

3.6免登陆

4过滤器方式

4.1maven---直接引用--配置

4.2 entity

4.3 filter

   4.3.1 访问过滤器  获取request--token

 4.3.2 登录过滤器   成功 获取token 保存redis

 4.4密码的处理方法类型

 4.4退出

4.5token生成

4.6未授权

4.7 XML

4.7 jwt

4.8 MD5

4.9 R


Spring Cloud Gateway是基于Spring Boot 2.xSpring WebFluxProject Reactor构建的。结果,当您使用Spring Cloud Gateway时,许多您熟悉的同步库(例如,Spring Data和Spring Security)和模式可能不适用。如果您不熟悉这些项目,建议您在使用Spring Cloud Gateway之前先阅读它们的文档以熟悉一些新概念。

Spring-Security

Spring Security是一个提供身份验证,授权和保护以防止常见攻击的框架。凭借对命令式和响应式应用程序的一流支持,它是用于保护基于Spring的应用程序的事实上的标准。

Spring-Webflux

Spring框架中包含的原始Web框架Spring Web MVC是专门为Servlet API和Servlet容器而构建的。响应式堆栈Web框架Spring WebFlux在稍后的5.0版中添加。它是完全无阻塞的,支持 Reactive Streams背压,并在Netty,Undertow和Servlet 3.1+容器等服务器上运行。

这两个Web框架都反映了其源模块的名称(spring-webmvcspring-webflux),并在Spring Framework中并存。每个模块都是可选的。应用程序可以使用一个模块,也可以使用两个模块,在某些情况下,也可以使用两个模块,例如,带有react的Spring MVC控制器WebClient

注意

由于Web容器不同,在Gateway项目中使用的WebFlux,是不能和Spring-Web混合使用的。 Spring MVC和 WebFlux 的区别:


编码

项目环境版本

  1. Spring-Cloud:2020.0.1
  2. Spring-Boot: 2.4.3

gradle 依赖

dependencies 
 implementation(
            'org.springframework.cloud:spring-cloud-starter-gateway',
            'org.springframework.boot:spring-boot-starter-security'
 )

复制代码

Spring-Security配置

spring security设置要采用响应式配置,基于WebFlux中WebFilter实现,与Spring MVC的Security是通过Servlet的Filter实现类似,也是一系列filter组成的过滤链。

Reactor与传统MVC配置对应:

webfluxmvc作用
@EnableWebFluxSecurity@EnableWebSecurity开启security配置
ServerAuthenticationSuccessHandlerAuthenticationSuccessHandler登录成功Handler
ServerAuthenticationFailureHandlerAuthenticationFailureHandler登陆失败Handler
ReactiveAuthorizationManagerAuthorizationManager认证管理
ServerSecurityContextRepositorySecurityContextHolder认证信息存储管理
ReactiveUserDetailsServiceUserDetailsService用户登录
ReactiveAuthorizationManagerAccessDecisionManager鉴权管理
ServerAuthenticationEntryPointAuthenticationEntryPoint未认证Handler
ServerAccessDeniedHandlerAccessDeniedHandler鉴权失败Handler

1. Security核心配置

package com.pluto.gateway.security;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.DelegatingReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.util.LinkedList;

/**
 * @author ShiLei
 * @version 1.0.0
 * @date 2021/3/11 10:56
 * @description webflux security核心配置类
 */
@EnableWebFluxSecurity
public class WebfluxSecurityConfig 
    @Resource
    private DefaultAuthorizationManager defaultAuthorizationManager;
    
    @Resource
    private UserDetailsServiceImpl userDetailsServiceImpl;
    
    @Resource
    private DefaultAuthenticationSuccessHandler defaultAuthenticationSuccessHandler;
    
    @Resource
    private DefaultAuthenticationFailureHandler defaultAuthenticationFailureHandler;
    
    @Resource
    private TokenAuthenticationManager tokenAuthenticationManager;
    
    @Resource
    private DefaultSecurityContextRepository defaultSecurityContextRepository;
    
    @Resource
    private DefaultAuthenticationEntryPoint defaultAuthenticationEntryPoint;
    
    @Resource
    private DefaultAccessDeniedHandler defaultAccessDeniedHandler;
    
    /**
    * 自定义过滤权限
    */
    @Value("$security.noFilter")
    private String noFilter;
     
     @Bean
     public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity httpSecurity) 
        httpSecurity
                // 登录认证处理
                .authenticationManager(reactiveAuthenticationManager())
                .securityContextRepository(defaultSecurityContextRepository)
                // 请求拦截处理
                .authorizeExchange(exchange -> exchange
                        .pathMatchers(noFilter).permitAll()
                        .pathMatchers(HttpMethod.OPTIONS).permitAll()
                        .anyExchange().access(defaultAuthorizationManager)
                )
                .formLogin()
                // 自定义处理
                .authenticationSuccessHandler(defaultAuthenticationSuccessHandler)
                        .authenticationFailureHandler(defaultAuthenticationFailureHandler)
                .and()
                .exceptionHandling()
                .authenticationEntryPoint(defaultAuthenticationEntryPoint)
                .and()
                .exceptionHandling()
                .accessDeniedHandler(defaultAccessDeniedHandler)
                .and()
                .csrf().disable()
        ;
        return httpSecurity.build();
     
     
     /**
     * BCrypt密码编码
     */
     @Bean("passwordEncoder")
     public PasswordEncoder passwordEncoder() 
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
     
     
     /**
     * 注册用户信息验证管理器,可按需求添加多个按顺序执行
     */
     @Bean
     ReactiveAuthenticationManager reactiveAuthenticationManager() 
            LinkedList<ReactiveAuthenticationManager> managers = new LinkedList<>();
         managers.add(authentication -> 
                    // 其他登陆方式 (比如手机号验证码登陆) 可在此设置不得抛出异常或者 Mono.error 
                    return Mono.empty();
         );
         // 必须放最后不然会优先使用用户名密码校验但是用户名密码不对时此 AuthenticationManager 会调用 Mono.error 造成后面的 AuthenticationManager 不生效
         managers.add(new UserDetailsRepositoryReactiveAuthenticationManager(userDetailsServiceImpl));
         managers.add(tokenAuthenticationManager);
         return new DelegatingReactiveAuthenticationManager(managers);
     

复制代码

2.用户认证

package com.pluto.gateway.security;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import java.io.Serializable;
import java.util.Collection;

/**
 * @author ShiLei
 * @version 1.0.0
 * @date 2021/3/10 13:15
 * @description 自定义用户信息
 */
public class SecurityUserDetails extends User implements Serializable 

    private Long userId;
    
    public SecurityUserDetails(String username, String password, Collection<? extends GrantedAuthority> authorities, Long userId) 
        super(username, password, authorities);
        this.userId = userId;
    
    
    public SecurityUserDetails(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities, Long userId) 
        super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
        this.userId = userId;
    
    
    public Long getUserId() 
        return userId;
    
    
    public void setUserId(Long userId) 
        this.userId = userId;
    


复制代码
package com.pluto.gateway.security;

import org.springframework.security.core.userdetails.ReactiveUserDetailsPasswordService;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.util.ArrayList;

/**
 * @author ceshi
 * @date 2021/3/9 14:03
 * @description 用户登录处理
 * @version 1.0.0
 */@Service
public class UserDetailsServiceImpl implements ReactiveUserDetailsService 

    @Resource
    private PasswordEncoder passwordEncoder;
    
    @Override
    public Mono<UserDetails> findByUsername(String username) 
        SecurityUserDetails securityUserDetails = new SecurityUserDetails(
                    "user",
                    passwordEncoder.encode("user"),
                    true, true, true, true, new ArrayList<>(),
                    1L
        );
        return Mono.just(securityUserDetails);
    

复制代码

3.1 自定义登录成功Handler

package com.pluto.gateway.security;

import com.alibaba.fastjson.JSONObject;
import com.pluto.common.basic.utils.JwtTokenUtil;
import com.pluto.common.basic.utils.ResultVoUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;

/**
 * @author ShiLei
 * @version 1.0.0
 * @date 2021/3/11 15:00
 * @description 登录成功处理
 */
@Component
public class DefaultAuthenticationSuccessHandler implements ServerAuthenticationSuccessHandler 

    /**
    * token 过期时间
    */
    @Value("$jwt.token.expired")
    private int jwtTokenExpired;
    
    /**
    * 刷新token 时间
    */
    @Value("$jwt.token.refresh.expired")
    private int jwtTokenRefreshExpired;
    
    @Override
    public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) 
        return Mono.defer(() -> Mono.just(webFilterExchange.getExchange().getResponse()).flatMap(response -> 
            DataBufferFactory dataBufferFactory = response.bufferFactory();
            // 生成JWT token
            Map<String, Object> map = new HashMap<>(2);
            SecurityUserDetails userDetails = (SecurityUserDetails) authentication.getPrincipal();
            map.put("userId", userDetails.getUserId());
            map.put("username", userDetails.getUsername());
            map.put("roles",userDetails.getAuthorities());
            String token = JwtTokenUtil.generateToken(map, userDetails.getUsername(), jwtTokenExpired);
            String refreshToken = JwtTokenUtil.generateToken(map, userDetails.getUsername(), jwtTokenRefreshExpired);
            Map<String, Object> tokenMap = new HashMap<>(2);
            tokenMap.put("token", token);
            tokenMap.put("refreshToken", refreshToken);
            DataBuffer dataBuffer = dataBufferFactory.wrap(JSONObject.toJSONString(ResultVoUtil.success(tokenMap)).getBytes());
            return response.writeWith(Mono.just(dataBuffer));
        ));
     

复制代码

3.2 自定义登录失败Handler

package com.pluto.gateway.security;

import com.alibaba.fastjson.JSONObject;
import com.pluto.common.basic.enums.UserStatusCodeEnum;
import com.pluto.common.basic.utils.ResultVoUtil;
import com.pluto.common.basic.vo.ResultVO;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.security.authentication.*;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import java.util.Map;

/**
 * @author ShiLei
 * @version 1.0.0
 * @date 2021/3/11 15:14
 * @description 登录失败处理
 */
@Component
public class DefaultAuthenticationFailureHandler implements ServerAuthenticationFailureHandler 

    @Override
    public Mono<Void> onAuthenticationFailure(WebFilterExchange webFilterExchange, AuthenticationException exception) 
        return Mono.defer(() -> Mono.just(webFilterExchange.getExchange()
                                    .getResponse()).flatMap(response -> 
            DataBufferFactory dataBufferFactory = response.bufferFactory();
            ResultVO<Map<String, Object>> resultVO = ResultVoUtil.error();
            // 账号不存在
            if (exception instanceof UsernameNotFoundException) 
                resultVO = ResultVoUtil.failed(UserStatusCodeEnum.ACCOUNT_NOT_EXIST);
                // 用户名或密码错误
             else if (exception instanceof BadCredentialsException) 
                resultVO = ResultVoUtil.failed(UserStatusCodeEnum.LOGIN_PASSWORD_ERROR);
                // 账号已过期
             else if (exception instanceof AccountExpiredException) 
                resultVO = ResultVoUtil.failed(UserStatusCodeEnum.ACCOUNT_EXPIRED);
                // 账号已被锁定
             else if (exception instanceof LockedException) 
                resultVO = ResultVoUtil.failed(UserStatusCodeEnum.ACCOUNT_LOCKED);
                // 用户凭证已失效
             else if (exception instanceof CredentialsExpiredException) 
                resultVO = ResultVoUtil.failed(UserStatusCodeEnum.ACCOUNT_CREDENTIAL_EXPIRED);
                // 账号已被禁用
             else if (exception instanceof DisabledException) 
                resultVO = ResultVoUtil.failed(UserStatusCodeEnum.ACCOUNT_DISABLE);
            
            DataBuffer dataBuffer = dataBufferFactory.wrap(JSONObject.toJSONString(resultVO).getBytes());
            return response.writeWith(Mono.just(dataBuffer));
        ));
    

复制代码

3.3 自定义未认证Handler

package com.pluto.gateway.security;

import com.alibaba.fastjson.JSONObject;
import com.pluto.common.basic.enums.UserStatusCodeEnum;
import com.pluto.common.basic.utils.ResultVoUtil;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.Charset;

/**
 * @author ShiLei
 * @version 1.0.0
 * @date 2021/3/11 15:17
 * @description 未认证处理
 */
@Component
public class DefaultAuthenticationEntryPoint implements ServerAuthenticationEntryPoint 

    @Override
    public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException ex) 
        return Mono.defer(() -> Mono.just(exchange.getResponse())).flatMap(response ->   
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
                DataBufferFactory dataBufferFactory = response.bufferFactory();
                String result = JSONObject.toJSONString(ResultVoUtil.failed(UserStatusCodeEnum.USER_UNAUTHORIZED));
                DataBuffer buffer = dataBufferFactory.wrap(result.getBytes(
                                    Charset.defaultCharset()));
                return response.writeWith(Mono.just(buffer));
        );
    

复制代码

3.4 自定义鉴权失败Handler

package com.pluto.gateway.security;

import com.alibaba.fastjson.JSONObject;
import com.pluto.common.basic.enums.UserStatusCodeEnum;
import com.pluto.common.basic.utils.ResultVoUtil;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.Charset;

/**
 * @author ShiLei
 * @version 1.0.0
 * @date 2021/3/11 11:12
 * @description 鉴权管理
 */
@Component
public class DefaultAccessDeniedHandler implements ServerAccessDeniedHandler 

    @Override
    public Mono<Void> handle(ServerWebExchange exchange, AccessDeniedException denied) 
        return Mono.defer(() -> Mono.just(exchange.getResponse()))
                .flatMap(response -> 
                    response.setStatusCode(HttpStatus.OK);
                    response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
                    DataBufferFactory dataBufferFactory = response.bufferFactory();
                    String result = JSONObject.toJSONString(ResultVoUtil.failed(UserStatusCodeEnum.PERMISSION_DENIED));
                    DataBuffer buffer = dataBufferFactory.wrap(result.getBytes(
                                    Charset.defaultCharset()));
                    return response.writeWith(Mono.just(buffer));
        );
    

复制代码

4.自定义JWT Token认证管理

package com.pluto.gateway.security;

import org.apache.commons.lang3.StringUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.web.server.context.ServerSecurityContextRepository;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.util.List;

/**
 * @author ShiLei
 * @version 1.0.0
 * @date 2021/3/11 16:27
 * @description 存储认证授权的相关信息
 */
@Component
public class DefaultSecurityContextRepository implements ServerSecurityContextRepository 

    public final static String TOKEN_HEADER = "Authorization";
    
    public final static String BEARER = "Bearer ";
    
    @Resource
    private TokenAuthenticationManager tokenAuthenticationManager;
    
    @Override
    public Mono<Void> save(ServerWebExchange exchange, SecurityContext context) 
        return Mono.empty();
    
    
    @Override
    public Mono<SecurityContext> load(ServerWebExchange exchange) 
        ServerHttpRequest request = exchange.getRequest();
        List<String> headers = request.getHeaders().get(TOKEN_HEADER);
        if (!CollectionUtils.isEmpty(headers)) 
            String authorization = headers.get(0);
            if (StringUtils.isNotEmpty(authorization)) 
                String token = authorization.substring(BEARER.length());
                if (StringUtils.isNotEmpty(token)) 
                    return tokenAuthenticationManager.authenticate(
                        new UsernamePasswordAuthenticationToken(token, null)
                    ).map(SecurityContextImpl::new);
                
            
        
        return Mono.empty();
    

复制代码
package com.pluto.gateway.security;

import com.pluto.common.basic.utils.JwtTokenUtil;
import org.springframework.context.annotation.Primary;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import java.util.Collection;

/**
 * @author ShiLei
 * @version 1.0.0
 * @date 2021/3/11 13:23
 * @description token 认证处理
 */
@Component
@Primary
public class TokenAuthenticationManager implements ReactiveAuthenticationManager 

    @Override
    @SuppressWarnings("unchecked")
    public Mono<Authentication> authenticate(Authentication authentication) 
        return Mono.just(authentication)
                .map(auth -> JwtTokenUtil.parseJwtRsa256(auth.getPrincipal().toString()))
                .map(claims -> 
                    Collection<? extends GrantedAuthority> roles = (Collection<? extends GrantedAuthority>)                     claims.get("roles");
                    return new UsernamePasswordAuthenticationToken(
                            claims.getSubject(),
                            null,
                            roles
                    );
                );
    

复制代码

5.自定义鉴权管理

package com.pluto.gateway.security;

import com.alibaba.fastjson.JSONObject;
import com.pluto.common.basic.enums.UserStatusCodeEnum;
import com.pluto.common.basic.utils.ResultVoUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Collection;

/**
 * @author ShiLei
 * @version 1.0.0
 * @date 2021/3/11 13:10
 * @description 用户权限鉴权处理
 */
@Component
@Slf4j
public class DefaultAuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> 

    private final AntPathMatcher antPathMatcher = new AntPathMatcher();
    
    @Override
    public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, AuthorizationContext authorizationContext) 
        return authentication.map(auth -> 
            ServerWebExchange exchange = authorizationContext.getExchange();
            ServerHttpRequest request = exchange.getRequest();
            Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
            for (GrantedAuthority authority : authorities) 
                String authorityAuthority = authority.getAuthority();
                String path = request.getURI().getPath();
                // TODO
                // 查询用户访问所需角色进行对比
                if (antPathMatcher.match(authorityAuthority, path)) 
                    log.info(String.format("用户请求API校验通过,GrantedAuthority:%s  Path:%s ", authorityAuthority, path));
                return new AuthorizationDecision(true);
                
            
                return new AuthorizationDecision(false);
        ).defaultIfEmpty(new AuthorizationDecision(false));
    
    
    @Override
    public Mono<Void> verify(Mono<Authentication> authentication, AuthorizationContext object) 
        return check(authentication, object)
                .filter(AuthorizationDecision::isGranted)
                .switchIfEmpty(Mono.defer(() -> 
                    String body = JSONObject.toJSONString(ResultVoUtil.failed(UserStatusCodeEnum.PERMISSION_DENIED));
                    return Mono.error(new AccessDeniedException(body));
                )).flatMap(d -> Mono.empty());
    

复制代码

2.springsecruity密码判断

下面看看是哪里进行的密码比较

1 /spring-security-core-5.1.4.RELEASE-sources.jar!/org/springframework/security/authentication/dao/AbstractUserDetailsAuthenticationProvider.java

public Authentication authenticate(Authentication authentication)  
      throws AuthenticationException 
 
   ......
   try 
      preAuthenticationChecks.check(user);
      // 重点看  additionalAuthenticationChecks  密码判断
      additionalAuthenticationChecks(user,(UsernamePasswordAuthenticationToken) authentication);         
   
   catch (AuthenticationException exception) 
      if (cacheWasUsed) 
         // There was a problem, so try again after checking
         // we're using latest data (i.e. not from the cache)
         cacheWasUsed = false;
         user = retrieveUser(username,
               (UsernamePasswordAuthenticationToken) authentication);
         preAuthenticationChecks.check(user);
         additionalAuthenticationChecks(user,
               (UsernamePasswordAuthenticationToken) authentication);
      
      else 
         throw exception;
      
   
 
 
   postAuthenticationChecks.check(user);
 
 
  ......
 
   return createSuccessAuthentication(principalToReturn, authentication, user);



 

2 /spring-security-core-5.1.4.RELEASE-sources.jar!/org/springframework/security/authentication/dao/DaoAuthenticationProvider.java

protected void additionalAuthenticationChecks(UserDetails userDetails,
      UsernamePasswordAuthenticationToken authentication)
      throws AuthenticationException 
   if (authentication.getCredentials() == null) 
      logger.debug("Authentication failed: no credentials provided");
 
 
      throw new BadCredentialsException(messages.getMessage(
            "AbstractUserDetailsAuthenticationProvider.badCredentials",
            "Bad credentials"));
   
 
 
   String presentedPassword = authentication.getCredentials().toString();
   // 密码比较就在这个地方,前面这个是用户输入的密码,后面这个是数据库存的密码,一致则通过
   if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) 
      logger.debug("Authentication failed: password does not match stored value");
 
 
      throw new BadCredentialsException(messages.getMessage(
            "AbstractUserDetailsAuthenticationProvider.badCredentials",
            "Bad credentials"));
   

3.流程

 

 

 

用户信息 还可以存放权限信息

 

3.尚硅谷springsecurity

 

 

 

3.3两个重要接口 

3.3.1认证

 

 

 3.3.2自定义登入

3.3.3

 

 

 403设计

3.5注解访问

 

 3.5用户注销

3.6免登陆

 

 

密码

4过滤器方式

4.1maven---直接引用--配置

package com.atguigu.serurity.config;

import com.atguigu.serurity.filter.TokenAuthenticationFilter;
import com.atguigu.serurity.filter.TokenLoginFilter;
import com.atguigu.serurity.security.DefaultPasswordEncoder;
import com.atguigu.serurity.security.TokenLogoutHandler;
import com.atguigu.serurity.security.TokenManager;
import com.atguigu.serurity.security.UnauthorizedEntryPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;

/**
 * <p>
 * Security配置类
 * </p>
 *
 * @author qy
 * @since 2019-11-18
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter 

    private UserDetailsService userDetailsService;//用户详情服务
    private TokenManager tokenManager;//令牌管理器
    private DefaultPasswordEncoder defaultPasswordEncoder;//默认密码编码器
    private RedisTemplate redisTemplate;//Redis 模板

    //令牌网络安全配置
    @Autowired
    public TokenWebSecurityConfig(UserDetailsService userDetailsService, DefaultPasswordEncoder defaultPasswordEncoder,
                                  TokenManager tokenManager, RedisTemplate redisTemplate) 
        this.userDetailsService = userDetailsService;
        this.defaultPasswordEncoder = defaultPasswordEncoder;
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
    

    /**
     * 配置设置
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception 
        System.out.println("configure(HttpSecurity http)"+http);
        http.exceptionHandling()
                .authenticationEntryPoint(new UnauthorizedEntryPoint())
                .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 TokenAuthenticationFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic();
    

    /**
     * 密码处理
     * @param auth
     * @throws Exception
     * 身份验证管理器生成器
     */
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception 
        auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder);
    

    /**
     * 配置哪些请求不拦截
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception 
        web.ignoring().antMatchers("/api/**",
                "/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**"
               );
    

4.2 entity

package com.atguigu.serurity.entity;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * <p>
 * 安全认证用户详情信息
 * </p>
 *
 * @author qy
 * @since 2019-11-08
 */
@Data
@Slf4j
public class SecurityUser implements UserDetails 

    //当前登录用户
    private transient User currentUserInfo;

    //当前权限
    private List<String> permissionValueList;

    public SecurityUser() 
    

    public SecurityUser(User user) 
        if (user != null) 
            this.currentUserInfo = user;
        
    

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() 
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        for(String permissionValue : permissionValueList) 
            if(StringUtils.isEmpty(permissionValue)) continue;
            SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
            authorities.add(authority);
        

        return authorities;
    

    @Override
    public String getPassword() 
        return currentUserInfo.getPassword();
    

    @Override
    public String getUsername() 
        return currentUserInfo.getUsername();
    

    @Override
    public boolean isAccountNonExpired() 
        return true;
    

    @Override
    public boolean isAccountNonLocked() 
        return true;
    

    @Override
    public boolean isCredentialsNonExpired() 
        return true;
    

    @Override
    public boolean isEnabled() 
        return true;
    

package com.atguigu.serurity.entity;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data;

import java.io.Serializable;

/**
 * <p>
 * 用户实体类
 * </p>
 *
 * @author qy
 * @since 2019-11-08
 */
@Data
@ApiModel(description = "用户实体类")
public class User implements Serializable 

	private static final long serialVersionUID = 1L;

	@ApiModelProperty(value = "微信openid")
	private String username;

	@ApiModelProperty(value = "密码")
	private String password;

	@ApiModelProperty(value = "昵称")
	private String nickName;

	@ApiModelProperty(value = "用户头像")
	private String salt;

	@ApiModelProperty(value = "用户签名")
	private String token;





4.3 filter

   4.3.1 访问过滤器  获取request--token

package com.atguigu.serurity.filter;

import com.atguigu.commonutils.R;
import com.atguigu.commonutils.ResponseUtil;
import com.atguigu.serurity.security.TokenManager;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.StringUtils;

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.Collection;
import java.util.List;

/**
 * <p>
 *               访问过滤器
 * </p>
 *
 * @author qy
 * @since 2019-11-08
 */
public class TokenAuthenticationFilter extends BasicAuthenticationFilter 
    private TokenManager tokenManager;
    private RedisTemplate redisTemplate;

    public TokenAuthenticationFilter(AuthenticationManager authManager, TokenManager tokenManager,RedisTemplate redisTemplate) 
        super(authManager);
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
    

    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
            throws IOException, ServletException 
        logger.info("=======TokenAuthenticationFilter-doFilterInternal=========="+req.getRequestURI());
        System.out.println("req.getRequestURI().indexOf(\\"admin\\")"+req.getRequestURI().indexOf("admin"));
        System.out.println("doFilterInternal-req"+req);
        System.out.println("doFilterInternal-req.getRequestURI()"+req.getRequestURI());
//        if(req.getRequestURI().indexOf("admin") == 1) 
//            chain.doFilter(req, res);
//            return;
//        

        UsernamePasswordAuthenticationToken authentication = null;
        try 
            authentication = getAuthentication(req);
            System.out.println("doFilterInternal-authentication"+authentication);
         catch (Exception e) 
            ResponseUtil.out(res, R.error());
        

        if (authentication != null) 
            SecurityContextHolder.getContext().setAuthentication(authentication);
         else 
            ResponseUtil.out(res, R.error());
        
        /*将请求转发给过滤器链上下一个对象。这里的下一个指的是下一个filter,
        如果没有filter那就是你请求的资源。 一般filter都是一个链,web.xml 里面配置了几个就有几个。
        一个一个的连在一起 request -> filter1 -> filter2 ->filter3 -> .... -> request resource.
*/
        chain.doFilter(req, res);
    

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) 
        // token置于header里
        String token = request.getHeader("token");
        if (token != null && !"".equals(token.trim())) 
            String userName = tokenManager.getUserFromToken(token);

            List<String> permissionValueList = (List<String>) redisTemplate.opsForValue().get(userName);
            Collection<GrantedAuthority> authorities = new ArrayList<>();
            for(String permissionValue : permissionValueList) 
                if(StringUtils.isEmpty(permissionValue)) continue;
                SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
                authorities.add(authority);
            

            if (!StringUtils.isEmpty(userName)) 
                return new UsernamePasswordAuthenticationToken(userName, token, authorities);
            
            return null;
        
        return null;
    

 4.3.2 登录过滤器   成功 获取token 保存redis

package com.atguigu.serurity.filter;

import com.atguigu.commonutils.R;
import com.atguigu.commonutils.ResponseUtil;
import com.atguigu.serurity.entity.SecurityUser;
import com.atguigu.serurity.entity.User;
import com.atguigu.serurity.security.TokenManager;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

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;

/**
 * <p>
 * 登录过滤器,继承UsernamePasswordAuthenticationFilter,对用户名密码进行登录校验
 * </p>
 *
 * @author qy
 * @since 2019-11-08
 */
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter 

    private AuthenticationManager authenticationManager;
    private TokenManager tokenManager;
    private RedisTemplate redisTemplate;

    public TokenLoginFilter(AuthenticationManager authenticationManager, TokenManager tokenManager, RedisTemplate redisTemplate) 
        this.authenticationManager = authenticationManager;
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
        this.setPostOnly(false);
        this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login","POST"));
    

    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
            throws AuthenticationException 
        try 
            User user = new ObjectMapper().readValue(req.getInputStream(), User.class);

            return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new ArrayList<>()));
         catch (IOException e) 
            throw new RuntimeException(e);
        

    

    /**
     * 登录成功
     * @param req
     * @param res
     * @param chain
     * @param auth
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain,
                                            Authentication auth) throws IOException, ServletException 
        SecurityUser user = (SecurityUser) auth.getPrincipal();
        String token = tokenManager.createToken(user.getCurrentUserInfo().getUsername());
        redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(), user.getPermissionValueList());

        ResponseUtil.out(res, R.ok().data("token", token));
    

    /**
     * 登录失败
     * @param request
     * @param response
     * @param e
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                                              AuthenticationException e) throws IOException, ServletException 
        ResponseUtil.out(response, R.error());
    

 4.4密码的处理方法类型

package com.atguigu.serurity.security;

import com.atguigu.commonutils.MD5;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

/**
 * <p>
 * t密码的处理方法类型
 * </p>
 *
 * @author qy
 * @since 2019-11-08
 */
@Component
public class DefaultPasswordEncoder implements PasswordEncoder 

    public DefaultPasswordEncoder() 
        this(-1);
    

    /**
     * @param strength
     *            the log rounds to use, between 4 and 31
     */
    public DefaultPasswordEncoder(int strength) 

    
   /*
   * 密码加密
   * */
    public String encode(CharSequence rawPassword) 
        return MD5.encrypt(rawPassword.toString());
    

    /*
    * 密码是否相等
    * */
    public boolean matches(CharSequence rawPassword, String encodedPassword) 

        return encodedPassword.equals(MD5.encrypt(rawPassword.toString()));
    

 4.4退出

package com.atguigu.serurity.security;

import com.atguigu.commonutils.R;
import com.atguigu.commonutils.ResponseUtil;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutHandler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * <p>
 * 登出业务逻辑类
 * </p>
 *
 * @author qy
 * @since 2019-11-08
 */
public class TokenLogoutHandler implements LogoutHandler 

    private TokenManager tokenManager;
    private RedisTemplate redisTemplate;

    public TokenLogoutHandler(TokenManager tokenManager, RedisTemplate redisTemplate) 
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
    

    @Override
    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) 
        String token = request.getHeader("token");
        if (token != null) 
            tokenManager.removeToken(token);

            //清空当前用户缓存中的权限数据
            String userName = tokenManager.getUserFromToken(token);
            redisTemplate.delete(userName);
        
        ResponseUtil.out(response, R.ok());
    

4.5token生成

package com.atguigu.serurity.security;

import io.jsonwebtoken.CompressionCodecs;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * <p>
 * token管理
 * </p>
 *
 * @author qy
 * @since 2019-11-08
 */
@Component
public class TokenManager 

    private long tokenExpiration = 24*60*60*1000;
    private String tokenSignKey = "123456";

    public String createToken(String username) 
        String token = Jwts.builder().setSubject(username)
                .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
                .signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();
        return token;
    

    public String getUserFromToken(String token) 
        String user = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject();
        return user;
    

    public void removeToken(String token) 
        //jwttoken无需删除,客户端扔掉即可。
    


4.6未授权

package com.atguigu.serurity.security;

import com.atguigu.commonutils.R;
import com.atguigu.commonutils.ResponseUtil;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

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

/**
 * <p>
 * 未授权的统一处理方式
 * </p>
 *
 * @author qy
 * @since 2019-11-08
 */
public class UnauthorizedEntryPoint implements AuthenticationEntryPoint 

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws IOException, ServletException 

        ResponseUtil.out(response, R.error());
    

4.7 XML

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>commont</artifactId>
        <groupId>com.atguigu</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring_security</artifactId>

    <depe

spring cloud gateway整合sentinel作网关限流

说明: sentinel可以作为各微服务的限流,也可以作为gateway网关的限流组件。 spring cloud gateway有限流功能,但此处用sentinel来作为替待。

说明:sentinel流控可以放在gateway网关端,也可以放在各微服务端。

1,以父工程为基础,创建子工程

2,添加pom依赖

     <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

2,添加配置项

server:
  port: 9092
spring:
  cloud:
    nacos:
      discovery:
        register-enabled: false
        server-addr: localhost:8848
        namespace: c22e5019-0bee-43b1-b80b-fc0b9d847501
    sentinel:
      transport:
        dashboard: localhost:8080
        port: 8719
      scg:
        fallback:
          mode: response
          response-status: 455
          response-body: error!
    gateway:
      routes:
        - id: demo_route
          uri: lb://demo
          predicates:
            - Path=/demo/**
        - id: demo2_test
          uri: lb://demo2
          predicates:
            - Path=/user/**
  application:
    name: gateway-sentinel

scg.fallback为sentinel限流后的响应配置

3,启动类

@SpringBootApplication
@EnableDiscoveryClient
public class GatewaySentinelApplication {
    public static void main(String[] args) {
        
        SpringApplication.run(GatewaySentinelApplication.class, args);
    }
}

4,启动后,在sentinel控制台可以看到 gateway-sentinel 应用,可以通过控制台设置流控规则。

以上是关于Gateway 整合 Spring Security鉴权的主要内容,如果未能解决你的问题,请参考以下文章

spring cloud gateway整合sentinel作网关限流

PassJava 开源 : Spring Cloud 整合Gateway网关 #私藏项目实操分享#

Spring Cloud Gateway 整合Eureka路由转发

Nacos整合Spring Cloud Gateway组件

Spring Cloud Gateway 整合 sentinel 实现流控熔断

Spring Cloud Gateway 整合 sentinel 实现流控熔断