Spring Security + OAuth2 - 黑马程序员 (8. Spring Security实现分布式系统授权【从头重写】- Gateway-Order) 学习笔记

上一篇:Spring Security + OAuth2 (7. Spring Security实现分布式系统授权【从头重写】- UAA)

下一篇:Spring Security + OAuth2 (9. Spring Security实现分布式系统授权【从头重写】- 前端模块)

文章目录

  • 1. 编写网关模块 - Gateway
    • 1. 编写 Gateway 专用的工具类
    • 2. 编写白名单的配置类
    • 3. 权限验证处理类
    • 4. 权限过滤器
  • 2. 编写资源模块 - Order
  • 3. 测试网关模块

  • 代码地址:https://gitee.com/yuan934672344/demo-spring-security

1. 编写网关模块 - Gateway

  • 目录结构
    Spring Security + OAuth2 - 黑马程序员 (8. Spring Security实现分布式系统授权【从头重写】- Gateway-Order) 学习笔记_第1张图片

1. 编写 Gateway 专用的工具类

  1. AuthConstant, 用于鉴权相关的前缀

    public class AuthConstant {
        public static final String AUTHORITY_PREFIX = "ROLE_";
        public static final String AUTHORITY_CLAIM_NAME = "authorities";
    }
    
  2. IErrorCode, 返回的错误信息的接口

    public interface IErrorCode {
        long getCode();
        String getMessage();
    }
    
  3. ResultCode, 返回信息

    public enum ResultCode implements IErrorCode {
        SUCCESS(200, "操作成功"),
        FAILED(500, "操作失败"),
        VALIDATE_FAILED(404, "参数检验失败"),
        UNAUTHORIZED(401, "暂未登录或token已经过期"),
        FORBIDDEN(403, "没有相关权限");
        private long code;
        private String message;
        private ResultCode(long code, String message) {
            this.code = code;
            this.message = message;
        }
        @Override
        public long getCode() {
            return code;
        }
        @Override
        public String getMessage() {
            return message;
        }
    }
    

2. 编写白名单的配置类

  1. IgnoreUrlsConfig, 用于读取在 application.yml 中配置的白名单

    @Data
    @EqualsAndHashCode(callSuper = false)
    @Component
    @ConfigurationProperties(prefix="secure.ignore")
    public class IgnoreUrlsConfig {
        private List<String> urls;
    }
    
  2. IgnoreUrlsRemoveJwtFilter,白名单过滤器

    @Component
    @Slf4j
    public class IgnoreUrlsRemoveJwtFilter implements WebFilter {
        @Autowired
        private IgnoreUrlsConfig ignoreUrlsConfig;
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
            ServerHttpRequest request = exchange.getRequest();
            URI uri = request.getURI();
            String path = uri.getPath();
            String token = exchange.getRequest().getHeaders().getFirst("Authorization");
            String methodValue = request.getMethodValue();
            PathMatcher pathMatcher = new AntPathMatcher();
            //白名单路径移除JWT请求头
            List<String> ignoreUrls = ignoreUrlsConfig.getUrls();
            for (String ignoreUrl : ignoreUrls) {
                if (pathMatcher.match(ignoreUrl, path) &&
                    "GET".equalsIgnoreCase(methodValue) &&
                     (token == null || Objects.equals(token, ""))) {
                    request = exchange.getRequest().mutate().header("Authorization", "").build();
                    exchange = exchange.mutate().request(request).build();
                    return chain.filter(exchange);
                }
            }
            return chain.filter(exchange);
        }
    }
    

3. 权限验证处理类

  1. RestAuthenticationEntryPoint, 没有进行认证时的处理类

    @Component
    @Slf4j
    public class RestAuthenticationEntryPoint implements ServerAuthenticationEntryPoint {
        @Override
        public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException e) {
            log.info("<< AUTH >> ---  没有进行认证");
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.OK);
            response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
            String body= JSONUtil.toJsonStr(CommonResult.unauthorized(e.getMessage()));
            DataBuffer buffer =  response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));
            return response.writeWith(Mono.just(buffer));
        }
    }
    
  2. RestfulAccessDeniedHandler, 没有进行授权时的处理类

    @Component
    @Slf4j
    public class RestfulAccessDeniedHandler implements ServerAccessDeniedHandler {
        @Override
        public Mono<Void> handle(ServerWebExchange exchange, AccessDeniedException denied) {
            log.info("<< AUTH >> ---  没有进行授权");
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.OK);
            response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
            String body= JSONUtil.toJsonStr(CommonResult.forbidden(denied.getMessage()));
            DataBuffer buffer =  response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));
            return response.writeWith(Mono.just(buffer));
        }
    }
    
  3. AuthorizationManager, 用于获取权限,并验证

    @Component
    @Slf4j
    public class AuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {
    	@Autowired
    	private RedisUtil redisUtil;
    	@Override
    	public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {
    		//从Redis中获取当前路径可访问角色列表
    		URI uri = authorizationContext.getExchange().getRequest().getURI();
    		String method = authorizationContext.getExchange().getRequest().getMethodValue();
    		Object obj = redisUtil.hget(
    				"AUTH:RESOURCE_URL_ROLE_IDS_CEL",
    				method+"-"+uri.getPath()
    		);
    		log.info(method+"-"+uri.getPath());
    		List<String> authorities = Convert.toList(String.class,obj);
    		authorities = authorities.stream().map(i -> i = AuthConstant.AUTHORITY_PREFIX + i).collect(Collectors.toList());
    		log.info(String.valueOf(authorities));
    		//认证通过且角色匹配的用户可访问当前路径
    		return mono
    				.filter(Authentication::isAuthenticated)
    				.flatMapIterable(Authentication::getAuthorities)
    				.map(GrantedAuthority::getAuthority)
    				.any(authorities::contains)
    				.map(AuthorizationDecision::new)
    				.defaultIfEmpty(new AuthorizationDecision(false));
    	}
    }
    

4. 权限过滤器

  1. AuthGlobalFilter, 解析令牌的过滤器

    @Component
    @Slf4j
    public class AuthGlobalFilter implements GlobalFilter, Ordered {
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            String token = exchange.getRequest().getHeaders().getFirst("Authorization");
            if (StrUtil.isEmpty(token)) {
                return chain.filter(exchange);
            }
            try {
                //从token中解析用户信息并设置到Header中去
                String realToken = token.replace("Bearer ", "");
                JWSObject jwsObject = JWSObject.parse(realToken);
                String userStr = jwsObject.getPayload().toString();
                log.info("<< AUTH >> --- AuthGlobalFilter.filter() user:{ "+userStr+" }");
                try {
                    userStr = URLEncoder.encode(userStr, "UTF-8");
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
                ServerHttpRequest request = exchange.getRequest().mutate().header("user", userStr).build();
                exchange = exchange.mutate().request(request).build();
            } catch (ParseException e) {
                e.printStackTrace();
            }
            return chain.filter(exchange);
        }
        @Override
        public int getOrder() {
            return 0;
        }
    }
    
  2. ResourceServerConfig, 请求过滤器,过滤所有的请求是否属于白名单、是否有权限

    @AllArgsConstructor
    @Configuration
    @EnableWebFluxSecurity
    public class ResourceServerConfig {
        private final AuthorizationManager authorizationManager;
        private final IgnoreUrlsConfig ignoreUrlsConfig;
        private final RestfulAccessDeniedHandler restfulAccessDeniedHandler;
        private final RestAuthenticationEntryPoint restAuthenticationEntryPoint;
        private final IgnoreUrlsRemoveJwtFilter ignoreUrlsRemoveJwtFilter;
        @Bean
        public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
            http.oauth2ResourceServer().jwt()
                    .jwtAuthenticationConverter(jwtAuthenticationConverter());
            //自定义处理JWT请求头过期或签名错误的结果
            http.oauth2ResourceServer().authenticationEntryPoint(restAuthenticationEntryPoint);
            //对白名单路径,直接移除JWT请求头
            http.addFilterBefore(ignoreUrlsRemoveJwtFilter, SecurityWebFiltersOrder.AUTHENTICATION);
            http.authorizeExchange()
                    //白名单配置
                    .pathMatchers(ArrayUtil.toArray(ignoreUrlsConfig.getUrls(),String.class)).permitAll()
                    //鉴权管理器配置
                    .anyExchange().access(authorizationManager)
                    .and().exceptionHandling()
                    //处理未授权
                    .accessDeniedHandler(restfulAccessDeniedHandler)
                    //处理未认证
                    .authenticationEntryPoint(restAuthenticationEntryPoint)
                    .and().csrf().disable();
            return http.build();
        }
        @Bean
        public Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter() {
            JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
            jwtGrantedAuthoritiesConverter.setAuthorityPrefix(AuthConstant.AUTHORITY_PREFIX);
            jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(AuthConstant.AUTHORITY_CLAIM_NAME);
            JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
            jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
            return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
        }
    }
    

2. 编写资源模块 - Order

  1. 编写测试的接口类,TestController

    @RestController
    @RequestMapping("/test")
    public class TestController {
    	@GetMapping("/testMethodA")
    	public String testMethodA(){
    		System.out.println("testMethodA");
    		return "testMethodA";
    	}
    	@GetMapping("/testMethodB")
    	public String testMethodB(){
    		System.out.println("testMethodB");
    		return "testMethodB";
    	}
    }
    

3. 测试网关模块

  1. 先在数据库中添加两个角色:管理员、普通用户。
    • 管理员可以访问: testMethodA、testMethodB
    • 普通用户可以访问:testMethodA
  2. 启动 UAA、Gateway、Order 模块
  3. 管理员获取令牌
    Spring Security + OAuth2 - 黑马程序员 (8. Spring Security实现分布式系统授权【从头重写】- Gateway-Order) 学习笔记_第2张图片
  4. 带上令牌访问接口
    Spring Security + OAuth2 - 黑马程序员 (8. Spring Security实现分布式系统授权【从头重写】- Gateway-Order) 学习笔记_第3张图片
  5. 使用 普通用户 获取令牌
    Spring Security + OAuth2 - 黑马程序员 (8. Spring Security实现分布式系统授权【从头重写】- Gateway-Order) 学习笔记_第4张图片
  6. 使用普通用户请求两个接口
    Spring Security + OAuth2 - 黑马程序员 (8. Spring Security实现分布式系统授权【从头重写】- Gateway-Order) 学习笔记_第5张图片
  7. 测试成功。

你可能感兴趣的:(SpringCloud,Spring,Security)