发布时间:2025-06-24 18:54:28  作者:北方职教升学中心  阅读量:723


网关收到请求,直接转发给授权服务

3、白名单直接放行 * 2、校验token * 3、 * TODO:后期可以使用非对称加密 */ @Bean public JwtAccessTokenConverter jwtAccessTokenConverter(){ JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); // 设置秘钥 converter.setSigningKey(SIGN_KEY); return converter; } @Bean PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }}

MyAuthorizationConfig类

@Configuration@EnableAuthorizationServerpublic class MyAuthorizationConfig extends AuthorizationServerConfigurerAdapter {   @Autowired   private TokenStore tokenStore;    /**     * 客户端存储策略,这里使用内存方式,后续可以存储在数据库     */    @Autowired    private ClientDetailsService clientDetailsService;    /**     * Security的认证管理器,密码模式需要用到     */    @Autowired    private AuthenticationManager authenticationManager;    @Autowired    private JwtAccessTokenConverter jwtAccessTokenConverter;    /**     * 配置令牌访问的安全约束     */    @Override    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {        security                //开启/oauth/token_key验证端口权限访问                .tokenKeyAccess("permitAll()")                //开启/oauth/check_token验证端口认证权限访问                .checkTokenAccess("permitAll()")                //表示支持 client_id 和 client_secret 做登录认证                .allowFormAuthenticationForClients();    }    //配置客户端    @Override    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {        //内存模式        clients.inMemory()                //客户端id                .withClient("test")                //客户端秘钥                .secret(new BCryptPasswordEncoder().encode("123456"))                //资源id,唯一,比如订单服务作为一个资源,可以设置多个                .resourceIds("order")                //授权模式,总共四种,1. authorization_code(授权码模式)、password(密码模式)、这个方法是为了在处理请求过程中创建一个新的请求对象,以便进行一些修改或增强。客户端发出请求给网关获取令牌

2、客户端携带令牌请求资源,请求直接到了网关层

5、 检查token是否存在 String token = getToken(exchange); if (StringUtils.isBlank(token)) { return invalidTokenMono(exchange); } //3 判断是否是有效的token OAuth2AccessToken oAuth2AccessToken; try { //解析token,使用tokenStore oAuth2AccessToken = tokenStore.readAccessToken(token); Map<String, Object> additionalInformation = oAuth2AccessToken.getAdditionalInformation(); System.out.println(additionalInformation); //取出用户身份信息 String user_name = additionalInformation.get("user_name").toString(); //获取用户权限 List<String> authorities = (List<String>) additionalInformation.get("authorities"); //将用户名和权限进行Base64加密 JSONObject jsonObject=new JSONObject(); jsonObject.put("user_name", user_name); jsonObject.put("authorities",authorities); String base = Base64.encode(jsonObject.toJSONString()); // ServerHttpRequest 中的 mutate 方法是用于创建一个修改后的请求对象的方法,而不改变原始请求对象。权限信息 String principal = jsonObject.getString("user_name"); JSONArray tempJsonArray = jsonObject.getJSONArray("authorities"); //权限 String[] authorities = tempJsonArray.toArray(new String[0]); //放入LoginVal LoginVal loginVal = new LoginVal(); loginVal.setUsername(principal); loginVal.setAuthorities(authorities); //放入request的attribute中 request.setAttribute("login_message",loginVal); } filterChain.doFilter(request,response); }}

ServiceController类

@RestControllerpublic class ServiceController {    @RequestMapping("/test")    public LoginVal test(HttpServletRequest httpServletRequest){       return  (LoginVal)httpServletRequest.getAttribute("login_message");    }}

3. Gateway网关

导入依赖

<dependencies>        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-gateway</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-security</artifactId>            <version>2.2.5.RELEASE</version>        </dependency>        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-oauth2</artifactId>            <version>2.2.5.RELEASE</version>        </dependency>        <dependency>            <groupId>org.springframework.security</groupId>            <artifactId>spring-security-oauth2-resource-server</artifactId>        </dependency>        <dependency>            <groupId>com.alibaba.cloud</groupId>            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>        </dependency>        <dependency>            <groupId>org.projectlombok</groupId>            <artifactId>lombok</artifactId>        </dependency>        <dependency>            <groupId>cn.hutool</groupId>            <artifactId>hutool-all</artifactId>        </dependency>        <dependency>            <groupId>com.alibaba</groupId>            <artifactId>fastjson</artifactId>            <version>1.2.78</version>        </dependency>        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-loadbalancer</artifactId>        </dependency>    </dependencies>

application.yaml

server:  port: 7000spring:  main:    allow-bean-definition-overriding: true  application:    name: oauth2-cloud-gateway  cloud:    nacos:      ## 注册中心配置      discovery:        # nacos的服务地址,nacos-server中IP地址:端口号        server-addr: 127.0.0.1:8848    gateway:      ## 路由      routes:        ## id只要唯一即可,名称任意        - id: oauth2-cloud-auth-server          uri: lb://oauth2-cloud-auth-server          predicates:            ## Path Route Predicate Factory断言            - Path=/oauth/**        - id: oauth2-cloud-order          uri: lb://oauth2-cloud-service          predicates:            ## Path Route Predicate Factory断言            - Path=/test/**oauth2:  cloud:    sys:      parameter:        ignoreUrls:          - /oauth/token          - /oauth/authorize

 AccessTokenConfig类

/** * 令牌的配置 */@Configurationpublic class AccessTokenConfig {    private final static String SIGN_KEY="jwt";    /**     * 令牌的存储策略     */    @Bean    public TokenStore tokenStore() {        //使用JwtTokenStore生成JWT令牌        return new JwtTokenStore(jwtAccessTokenConverter());    }    /**     * JwtAccessTokenConverter     * TokenEnhancer的子类,在JWT编码的令牌值和OAuth身份验证信息之间进行转换。

目录

1. OAuth2.0授权服务

2. 资源服务

3. Gateway网关

4. 测试


 

在SpringSecurity+OAuth2.0 搭建认证中心和资源服务中心-CSDN博客 ​​​​​​

基础上整合网关和JWT实现分布式统一认证授权。implicit(简化模式) //refresh_token并不是授权模式, .authorizedGrantTypes("authorization_code","password","client_credentials","implicit","refresh_token") //允许的授权范围,客户端的权限,这里的all只是一种标识,可以自定义,为了后续的资源服务进行权限控制 .scopes("all") //false 则跳转到授权页面 .autoApprove(false) //授权码模式的回调地址 .redirectUris("http://www.baidu.com"); //可以and继续添加客户端 } @Bean public AuthorizationServerTokenServices tokenServices() { DefaultTokenServices services = new DefaultTokenServices(); //客户端端配置策略 services.setClientDetailsService(clientDetailsService); //支持令牌的刷新 services.setSupportRefreshToken(true); //令牌服务 services.setTokenStore(tokenStore); //access_token的过期时间 services.setAccessTokenValiditySeconds(60 * 60 * 2); //refresh_token的过期时间 services.setRefreshTokenValiditySeconds(60 * 60 * 24 * 3); //设置令牌增强,使用jwt services.setTokenEnhancer(jwtAccessTokenConverter); return services; } /** * 授权码模式的service,使用授权码模式authorization_code必须注入 */ @Bean public AuthorizationCodeServices authorizationCodeServices() { //授权码存在内存中 return new InMemoryAuthorizationCodeServices(); } /** * 配置令牌访问的端点 */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints //授权码模式所需要的authorizationCodeServices .authorizationCodeServices(authorizationCodeServices()) //密码模式所需要的authenticationManager .authenticationManager(authenticationManager) //令牌管理服务,无论哪种模式都需要 .tokenServices(tokenServices()) //只允许POST提交访问令牌,uri:/oauth/token .allowedTokenEndpointRequestMethods(HttpMethod.POST); }}

SecurityConfig类

/** * spring security的安全配置 */@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {    /**     * 加密算法     */    @Autowired    JwtTokenUserDetailsService userDetailsService;    @Override    protected void configure(HttpSecurity http) throws Exception {        //todo 允许表单登录        http.authorizeRequests()                .anyRequest().authenticated()                .and()                .formLogin()                .loginProcessingUrl("/login")                .permitAll()                .and()                .csrf()                .disable();    }    @Override    protected void configure(AuthenticationManagerBuilder auth) throws Exception {       //从数据库中查询用户信息        auth.userDetailsService(userDetailsService);    }    /**     * AuthenticationManager对象在OAuth2认证服务中要使用,提前放入IOC容器中     * Oauth的密码模式需要     */    @Override    @Bean    public AuthenticationManager authenticationManagerBean() throws Exception {        return super.authenticationManagerBean();    }}

2. 资源服务

导入依赖

<dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>        <dependency>        <groupId>cn.hutool</groupId>        <artifactId>hutool-all</artifactId>       </dependency>        <dependency>            <groupId>com.alibaba</groupId>            <artifactId>fastjson</artifactId>            <version>1.2.78</version>        </dependency>        <dependency>            <groupId>org.projectlombok</groupId>            <artifactId>lombok</artifactId>            <version>1.18.30</version>        </dependency>        <dependency>            <groupId>com.alibaba.cloud</groupId>            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>        </dependency>

application.yaml

server:  port: 8081spring:  application:    name: oauth2-cloud-service  cloud:    nacos:      ## 注册中心配置      discovery:        # nacos的服务地址,nacos-server中IP地址:端口号        server-addr: 127.0.0.1:8848

 AuthenticationFilter类

@Componentpublic class AuthenticationFilter extends OncePerRequestFilter {    /**     * 具体方法主要分为两步     * 1. 解密网关传递的信息     * 2. 将解密之后的信息封装放入到request中     */    @Override    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {        //获取请求头中的用户信息        String token = request.getHeader("token");        if (token!=null){            //解密            String json = Base64.decodeStr(token);            JSONObject jsonObject = JSON.parseObject(json);            //获取用户身份信息、client_credentials(客户端模式)、            ServerHttpRequest tokenRequest = exchange.getRequest().mutate().header("token",base).build();            ServerWebExchange build = exchange.mutate().request(tokenRequest).build();            return chain.filter(build);        } catch (InvalidTokenException e) {            //解析token异常,直接返回token无效            return invalidTokenMono(exchange);        }    }    @Override    public int getOrder() {        return 0;    }    /**     * 对url进行校验匹配     */    private boolean checkUrls(List<String> urls,String path){        AntPathMatcher pathMatcher = new AntPathMatcher();        for (String url : urls) {            if (pathMatcher.match(url,path))                return true;        }        return false;    }    /**     * 从请求头中获取Token     */    private String getToken(ServerWebExchange exchange) {        String tokenStr = exchange.getRequest().getHeaders().getFirst("Authorization");        if (StringUtils.isBlank(tokenStr)) {            return null;        }        String token = tokenStr.split(" ")[1];        if (StringUtils.isBlank(token)) {            return null;        }        return token;    }    /**     * 无效的token     */    private Mono<Void> invalidTokenMono(ServerWebExchange exchange) {        return buildReturnMono(ResultMsg.builder()                .code(1004)                .msg("无效的token")                .build(), exchange);    }    private Mono<Void> buildReturnMono(ResultMsg resultMsg, ServerWebExchange exchange) {        ServerHttpResponse response = exchange.getResponse();        byte[] bits = JSON.toJSONString(resultMsg).getBytes(StandardCharsets.UTF_8);        DataBuffer buffer = response.bufferFactory().wrap(bits);        response.setStatusCode(HttpStatus.UNAUTHORIZED);        response.getHeaders().add("Content-Type", "application/json;charset:utf-8");        return response.writeWith(Mono.just(buffer));    }}

SysParameterConfig

@ConfigurationProperties(prefix = "oauth2.cloud.sys.parameter")@Data@Componentpublic class SysParameterConfig {    /**     * 白名单     */    private List<String> ignoreUrls;}

4. 测试

 

代码链接:Gateway+Springsecurity+OAuth2.0+JWT实现分布式统一认证授权资源-CSDN文库 

过期时间校验....)、白名单放行,比如授权服务、静态资源..... if (checkUrls(sysConfig.getIgnoreUrls(),requestUrl)){ return chain.filter(exchange); } //2、 * TODO:后期可以使用非对称加密 */ @Bean public JwtAccessTokenConverter jwtAccessTokenConverter(){ JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); // 设置秘钥 converter.setSigningKey(SIGN_KEY); return converter; }}

JwtAccessManager类

@Slf4j@Component//经过认证管理器JwtAuthenticationManager认证成功后,就需要对令牌进行鉴权,如果该令牌无访问资源的权限,则不允通过。微服务进行逻辑处理

1. OAuth2.0授权服务

导入依赖

<dependencies>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-security</artifactId>            <version>2.2.5.RELEASE</version>        </dependency>        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-oauth2</artifactId>            <version>2.2.5.RELEASE</version>        </dependency>        <dependency>            <groupId>mysql</groupId>            <artifactId>mysql-connector-java</artifactId>            <version>8.0.28</version>        </dependency>        <dependency>            <groupId>com.baomidou</groupId>            <artifactId>mybatis-plus-boot-starter</artifactId>            <version>3.3.2</version>        </dependency>        <dependency>            <groupId>org.springframework.security</groupId>            <artifactId>spring-security-oauth2-resource-server</artifactId>        </dependency>        <dependency>            <groupId>org.projectlombok</groupId>            <artifactId>lombok</artifactId>            <version>1.18.30</version>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-test</artifactId>        </dependency>        <dependency>            <groupId>com.alibaba.cloud</groupId>            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>        </dependency>    </dependencies>

application.yaml

server:  port: 8080spring:  application:    name: oauth2-cloud-auth-server  cloud:    nacos:      ## 注册中心配置      discovery:        # nacos的服务地址,nacos-server中IP地址:端口号        server-addr: 127.0.0.1:8848  datasource:    driver-class-name: com.mysql.cj.jdbc.Driver    url: jdbc:mysql://localhost:3306/rbac?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC    username: root    password: 123456

 这里展示部分代码

AccessTokenConfig类

/** * 令牌的配置 */@Configurationpublic class AccessTokenConfig {    /**     * JWT的秘钥     * TODO 实际项目中需要统一配置到配置文件中,资源服务也需要用到     */    private final static String SIGN_KEY="jwt";    /**     * 令牌的存储策略     */    @Bean    public TokenStore tokenStore() {        //使用JwtTokenStore生成JWT令牌        return new JwtTokenStore(jwtAccessTokenConverter());    }    /**     * JwtAccessTokenConverter     * TokenEnhancer的子类,在JWT编码的令牌值和OAuth身份验证信息之间进行转换。授权服务验证用户名、public class JwtAccessManager implements ReactiveAuthorizationManager<AuthorizationContext> {    @Override    public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {        URI uri = authorizationContext.getExchange().getRequest().getURI();        //设计权限角色,这里简单写一下,实际上应该从数据库或者缓存中获取        List<String> authorities = new ArrayList<>();        authorities.add("ROLE_admin");        //认证通过且角色匹配的用户可访问当前路径        return mono                //判断是否认证成功                .filter(Authentication::isAuthenticated)                //获取认证后的全部权限                .flatMapIterable(Authentication::getAuthorities)                .map(GrantedAuthority::getAuthority)                //如果权限包含则判断为true                .any(authorities::contains)                .map(AuthorizationDecision::new)                .defaultIfEmpty(new AuthorizationDecision(false));    }}

JwtAuthenticationManager类

/** * JWT认证管理器,主要的作用就是对携带过来的token进行校验,比如过期时间,加密方式等 * 一旦token校验通过,则交给鉴权管理器进行鉴权 */@Componentpublic class JwtAuthenticationManager implements ReactiveAuthenticationManager {    /**     * 使用JWT令牌进行解析令牌     */    @Autowired    private TokenStore tokenStore;    @Override    public Mono<Authentication> authenticate(Authentication authentication) {        return Mono.justOrEmpty(authentication)                .filter(a -> a instanceof BearerTokenAuthenticationToken)                .cast(BearerTokenAuthenticationToken.class)                .map(BearerTokenAuthenticationToken::getToken)                .flatMap((accessToken -> {                    OAuth2AccessToken oAuth2AccessToken = this.tokenStore.readAccessToken(accessToken);                    //根据access_token从数据库获取不到OAuth2AccessToken                    if (oAuth2AccessToken == null) {                        return Mono.error(new InvalidTokenException("无效的token!"));                    } else if (oAuth2AccessToken.isExpired()) {                        return Mono.error(new InvalidTokenException("token已过期!"));                    }                    OAuth2Authentication oAuth2Authentication = this.tokenStore.readAuthentication(accessToken);                    if (oAuth2Authentication == null) {                        return Mono.error(new InvalidTokenException("无效的token!"));                    } else {                        return Mono.just(oAuth2Authentication);                    }                })).cast(Authentication.class);    }}

SecurityConfig类

@Configuration@EnableWebFluxSecuritypublic class SecurityConfig {    /**     * JWT的鉴权管理器     */    @Autowired    private ReactiveAuthorizationManager<AuthorizationContext> accessManager;    @Autowired    private RequestAuthenticationEntryPoint requestAuthenticationEntryPoint;    @Autowired    private RequestAccessDeniedHandler requestAccessDeniedHandler;    /**     * 系统参数配置     */    @Autowired    private SysParameterConfig sysConfig;    /**     * token校验管理器     */    @Autowired    private ReactiveAuthenticationManager tokenAuthenticationManager;    @Autowired    private CorsFilter corsFilter;    @Bean    SecurityWebFilterChain webFluxSecurityFilterChain(ServerHttpSecurity http) throws Exception{        //认证过滤器,放入认证管理器tokenAuthenticationManager        AuthenticationWebFilter authenticationWebFilter = new AuthenticationWebFilter(tokenAuthenticationManager);        authenticationWebFilter.setServerAuthenticationConverter(new ServerBearerTokenAuthenticationConverter());        http                .httpBasic().disable()                .csrf().disable()                .authorizeExchange()                //白名单直接放行                .pathMatchers(ArrayUtil.toArray(sysConfig.getIgnoreUrls(),String.class)).permitAll()                //其他的请求必须鉴权,使用鉴权管理器                .anyExchange().access(accessManager)                //异常处理                .and().exceptionHandling()                .authenticationEntryPoint(requestAuthenticationEntryPoint)                .accessDeniedHandler(requestAccessDeniedHandler)                .and()                // 跨域过滤器                .addFilterAt(corsFilter, SecurityWebFiltersOrder.CORS)                //token的认证过滤器,用于校验token和认证                .addFilterAt(authenticationWebFilter, SecurityWebFiltersOrder.AUTHENTICATION);        return http.build();    }}

RequestAccessDeniedHandler

/** * 自定义返回结果:没有权限访问时 */@Componentpublic class RequestAccessDeniedHandler implements ServerAccessDeniedHandler {    @Override    public Mono<Void> handle(ServerWebExchange exchange, AccessDeniedException denied) {        ServerHttpResponse response = exchange.getResponse();        response.setStatusCode(HttpStatus.OK);        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);        String body= JSONUtil.toJsonStr(new ResultMsg(1005,"无权限访问",null));        DataBuffer buffer =  response.bufferFactory().wrap(body.getBytes(Charset.forName("UTF-8")));        return response.writeWith(Mono.just(buffer));    }}

GlobalAuthenticationFilter

/** * 全局过滤器,对token的拦截,解析token放入header中,便于下游微服务获取用户信息 * 分为如下几步: *  1、重新封装用户信息,加密成功json数据放入请求头中传递给下游微服务 */@Component@Slf4jpublic class GlobalAuthenticationFilter implements GlobalFilter, Ordered {    /**     * JWT令牌的服务     */    @Autowired    private TokenStore tokenStore;    /**     * 系统参数配置     */    @Autowired    private SysParameterConfig sysConfig;    @Override    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {        String requestUrl = exchange.getRequest().getPath().value();        //1、密码等一系列身份,通过则颁发令牌给客户端

4、

 

大致流程如下:

1、鉴权(对当前令牌携带的权限)和访问资源所需的权限进行比对,如果权限有交集则通过校验,直接转发给微服务

6、网关对令牌进行校验(验签、读取token中存放的用户信息 * 4、