之前利用SpringSecurity OAuth2搭建了一个认证服务器,并且结合网关搭建了一个安全认证的架构,而这个架构也是有缺点的,很明显的就是用户的信息是通过加在请求头到微服务去取出来的,这样就会容易泄露用户的信息并且很容易被别人截获请求伪造用户信息,而且网关每一次都要请求认证服务器去验证token是否正确,加重了认证服务器的负担。那么这里我们解决这两个问题用的就是jwt。

什么是JWT?

关于jwt的组成这里不再述说,大家可自行去看网上的其他博客,这里主要说说jwt的作用。

首先jwt也是利用的token去验证身份,但是这个token里面是有内容能被解析出来的,通常我们都把一些用户信息放在这里面,而jwt是通过base64去编码成的token,不是加密的,所以我们也尽量不要把一些用户重要的信息放在里面。那么既然jwt不是加密的那么我们用它有什么用的,其实它的主要作用并不是加密,而是防止伪造,jwt是通过一个key值去参与生成的,只要我们保证这个key值是不被别人所获取的,那么别人就无法伪造一个token去通过jwt的解析了。

认证架构

大体的实现架构就是这样,由于SpringSecurity OAuth2已经封装了JWT的具体认证逻辑,所以在网关这里我们就不用再利用过滤器去认证token了,而是把网关和其他的微服务都当作是资源服务器交给SpringSecurity OAuth2去认证token。

改造之前的工程

1.认证服务器改造

之前把token存储在数据库的这种方式换成jwt这种方式的,并且配置tokenKeyAccess("isAuthenticated()")表示从认证服务器中拿key值时需要客户端信息验证。

@Configuration
@EnableAuthorizationServer
public class OAuth2AuthServerConfig extends AuthorizationServerConfigurerAdapter {@Autowiredprivate PasswordEncoder passwordEncoder;@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate DataSource dataSource;@Autowiredprivate UserDetailServiceImpl userDetailService;@Beanpublic TokenStore tokenStore(){//return new JdbcTokenStore(dataSource);return new JwtTokenStore(jwtTokenEnhancer());}@Beanpublic JwtAccessTokenConverter jwtTokenEnhancer() {JwtAccessTokenConverter converter = new JwtAccessTokenConverter();converter.setSigningKey("123456");return converter;}/*** 配置authenticationManager用于认证的过程* @param endpoints* @throws Exception*/@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {endpoints//设置tokenStore,生成token时会向数据库中保存.userDetailsService(userDetailService).tokenStore(tokenStore()).tokenEnhancer(jwtTokenEnhancer()).authenticationManager(authenticationManager);}/*** 重写此方法用于声明认证服务器能认证的客户端信息* 相当于在认证服务器中注册哪些客户端(包括资源服务器)能访问* @param clients* @throws Exception*/@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//        clients.inMemory()
//                .withClient("orderApp")  //声明访问认证服务器的客户端
//                .secret(passwordEncoder.encode("123456"))  //客户端访问认证服务器需要带上的密码
//                .scopes("read","write")  //获取token包含的哪些权限
//                .accessTokenValiditySeconds(3600)  //token过期时间
//                .resourceIds("order-service")  //指明请求的资源服务器
//                .authorizedGrantTypes("password")  //密码模式
//                .and()
//                //资源服务器拿到了客户端请求过来的token之后会请求认证服务器去判断此token是否正确或者过期
//                //所以此时的资源服务器对于认证服务器来说也充当了客户端的角色
//                .withClient("order-service")
//                .secret(passwordEncoder.encode("123456"))
//                .scopes("read")
//                .accessTokenValiditySeconds(3600)
//                .resourceIds("order-service")
//                .authorizedGrantTypes("password");//把客户端的信息以及token都存储在数据库中clients.jdbc(dataSource);}@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) throws Exception {security.tokenKeyAccess("isAuthenticated()").checkTokenAccess("isAuthenticated()");}
}

2.网关改造

对于网关来说由于我们不用自己去实现验证token的逻辑了,而是完全交给SpringSecurity OAuth2去完成,所以我们把之前实现的filter可以不要了,然后加上SpringSecurity OAuth2的依赖

        <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-oauth2</artifactId></dependency>

在application.yml配置文件中加上:

security:oauth2:client:client-id: gatewayclient-secret: 123456resource:jwt:key-uri: http://localhost:9000/oauth/token_key  #服务一启动就会请求认证服务器拿到jwt的key值

这个配置就是在服务一启动的时候就会向认证服务器请求拿到key值(一定要认证服务器先启动,否则请求不到的话该服务也会启动失败),并且需要提供客户端(这里指网关)的client-id和client-secret给认证服务器,认证服务器去数据库中判断是否有该客户端的信息,有的话就返回key值给客户端,当有请求过来的时候网关会自动去拿到http basic规范中请求头的token然后通过拿到的key值去判断解析该token是否是正确,正确的话就把请求继续转发给下面的微服务。

3.微服务改造

这里和网关改造的地方差不多

        <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-oauth2</artifactId></dependency>
security:oauth2:client:client-id: order-serviceclient-secret: 123456resource:jwt:key-uri: http://localhost:9000/oauth/token_key  #服务一启动就会请求认证服务器拿到jwt的key值

当我们想获取用户信息的时候,我们可以在接口上加上@AuthenticationPrincipal注解,用法和之前的说的一样

@PostMapping("/create")public OrderInfo order(@RequestBody OrderInfo orderInfo,@AuthenticationPrincipal String username){PriceInfo priceInfo = restTemplate.getForObject("http://localhost:9002/price/get", PriceInfo.class);orderInfo.setPrice(priceInfo.getPrice());log.info("order() username=========" + username);return orderInfo;}

权限控制

上面我们只是说了token的认证,这里我们说下关于权限的控制,对于OAuth2来说,权限的控制有两种方式,一种是通过客户端的scope来区分控制权限,这种是只针对客户端权限来说的,并不能针对到某一个用户的权限进行区分,而第二种就是能针对到具体的用户进行区分权限了,我们这里分别来说说两种方式在SpringSecurity中是如何实现的。

1.通过客户端的Scope来区分权限

我们可以在微服务接口上面加上注解@PreAuthorize(“#oauth2.hasScope(‘具体的scope’)”)

    @PostMapping("/create")@PreAuthorize("#oauth2.hasScope('fly')")public OrderInfo order(@RequestBody OrderInfo orderInfo,@AuthenticationPrincipal String username){PriceInfo priceInfo = restTemplate.getForObject("http://localhost:9002/price/get", PriceInfo.class);orderInfo.setPrice(priceInfo.getPrice());log.info("order() username=========" + username);return orderInfo;}

并且要让该注解生效的话还需要在启动类上加上注解@EnableGlobalMethodSecurity(prePostEnabled=true)

@Configuration
@SpringBootApplication
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)  //让@PreAuthorize注解生效
public class OrderApplication {//    @Autowired
//    private RestTemplateBuilder restTemplateBuilder;public static void main(String[] args) {SpringApplication.run(OrderApplication.class, args);}@Beanpublic OAuth2RestTemplate oAuth2RestTemplate(OAuth2ProtectedResourceDetails resource, OAuth2ClientContext context) {return new OAuth2RestTemplate(resource,context);}
}

如果没有该权限的话

2.通过某个用户去进行区分权限

在微服务接口加上@PreAuthorize(“hasRole(‘具体的角色’)”)

    @PostMapping("/create")@PreAuthorize("hasRole('ROLE_ADMIN')")public OrderInfo order(@RequestBody OrderInfo orderInfo,@AuthenticationPrincipal String username){PriceInfo priceInfo = restTemplate.getForObject("http://localhost:9002/price/get", PriceInfo.class);orderInfo.setPrice(priceInfo.getPrice());log.info("order() username=========" + username);return orderInfo;}

这个role是在认证服务器进行用户验证(UserDetailServiceImpl)的时候查询数据库的得到的,然后存储在jwt的token中。

这种注解的方式虽然说能具体到某个用户的权限,但是每当我们改变权限规则的时候就要重启服务,这样有点麻烦,所以SpringSecurity OAuth2还提供了另外一种比较方便优雅的方式给我们去实现。

@Configuration
@EnableResourceServer  //把网关服务配置成一个资源服务器
public class GatewaySecurityConfig extends ResourceServerConfigurerAdapter {@Autowiredprivate GatewayWebSecurityExpressionHandler gatewayWebSecurityExpressionHandler;@Overridepublic void configure(ResourceServerSecurityConfigurer resources) throws Exception {resources.expressionHandler(gatewayWebSecurityExpressionHandler);}@Overridepublic void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/auth-center/**").permitAll()  //转发向认证服务器的请求不用进行认证.anyRequest().access("#permissionService.hasPermission(request, authentication)");}}

我们在配置中配置#permissionService.hasPermission(request, authentication),表示当请求过来的时候需要经过该方法判断是否有权限通过,但是框架并不知道这个表达式的意思,所以需要我们把这个表达式注册进框架使框架认识这个表达式,所以我们就需要一个适配器去配置了

@Component
public class GatewayWebSecurityExpressionHandler extends OAuth2WebSecurityExpressionHandler {@Autowiredprivate PermissionService permissionService;@Overrideprotected StandardEvaluationContext createEvaluationContextInternal(Authentication authentication, FilterInvocation invocation) {StandardEvaluationContext standardEvaluationContext = super.createEvaluationContextInternal(authentication, invocation);standardEvaluationContext.setVariable("permissionService",permissionService);return standardEvaluationContext;}
}

而与权限相关的逻辑我们就需要自己去实现一个类了,在这个类中主要进行权限的判断,可以调用redis或者数据库里面的权限数据去判断当前用户是否有权限请求当前资源,返回true表示允许请求,false表示拒绝请求。

@Slf4j
@Service
public class PermissionServiceImpl implements PermissionService {@Overridepublic boolean hasPermission(HttpServletRequest request, Authentication authentication) {log.info("request url : " + request.getRequestURI());log.info("authentication : " + authentication.toString());return RandomUtils.nextInt(0,20) % 2 == 0;}
}

处理日志记录

之前说过我们完整的一套认证流程是需要经过限流,认证,记录日志,授权这4个步骤的,而SpringSecurity OAuth2已经帮我们实现了认证和授权,如果我们需要加入记录日志这个步骤的话就需要自定义过滤器加入到SpringSecurity OAuth2的过滤器链中了。

加入一个过滤器很简单,实现一个web过滤器就可以了

@Slf4j
public class GatewayAuditLogFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {String username = (String) SecurityContextHolder.getContext().getAuthentication().getPrincipal();log.info("add log for user " + username);filterChain.doFilter(httpServletRequest,httpServletResponse);}
}

在这个日志过滤器中我们从SpringSecurity的上下文中取出当前请求认证通过的用户名并简单打印出来(如果客户端传过来的token时候错误的话,SpringSecurity OAuth2的认证过滤器会直接拒绝掉该请求并返回错误信息,此时就不会经过后面的日志过滤器了,而如果没有传token过来的话认证是可以通过的但是在日志过滤器这里拿到的用户名是一个匿名用户)

处理401状态码异常

继承OAuth2AuthenticationEntryPoint这个类,这个类是专门处理401异常的,如果传了错误的token或者没有传token并且没有配置该请求是不需要权限的,那么就会返回给客户端错误提示的json,这个json就是这个类生成的,所以我们如果需要记录401的日志的话,就需要继承这个类在里面去处理了

/*** OAuth2AuthenticationEntryPoint该类是专门处理401状态异常的*/
@Component
@Slf4j
public class GatewayAuthenticationEntryPoint extends OAuth2AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {//日志记录401log.info("add log 401");super.commence(request, response, authException);}
}

处理403状态码异常

继承OAuth2AccessDeniedHandler这个类,该类和上面一样,只不过是专门处理403状态异常的

/*** OAuth2AccessDeniedHandler该类是专门处理403状态异常的*/
@Component
@Slf4j
public class GatewayAccessDeniedHandler extends OAuth2AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException authException) throws IOException, ServletException {//记录日志log.info("add log 403");super.handle(request, response, authException);}
}

最后这两个状态异常都需要配置才能生效

@Overridepublic void configure(ResourceServerSecurityConfigurer resources) throws Exception {resources//.expressionHandler(gatewayWebSecurityExpressionHandler);.accessDeniedHandler(gatewayAccessDeniedHandler).authenticationEntryPoint(gatewayAuthenticationEntryPoint);}

最后,总的架构应该是这样的一个流程,前端通过登录经过网关转发从认证服务器那边拿到jwt token,之后每次请求都带上这个jwt token经过网关,在网关这边去进行认证鉴权,而网关鉴权需要的角色权限数据就是从数据库或者redis中拿到的,这些数据由权限服务(专门负责角色权限数据的服务)进行增删改到数据库或者redis中,网关认证鉴权通过了之后就能转发到后面相应的微服务了,而后面的微服务在拿到网关转发过来的jwt token之后又再进行认证,认证成功之后能顺利访问到了。

SpringSecurity OAuth2+JWT+网关实现认证授权中心相关推荐

  1. Spring Security Oauth2 JWT 实现用户认证授权功能

    Spring Security Oauth2 JWT 一 用户认证授权 1. 需求分析 1.1 用户认证与授权 什么是用户身份认证? 用户身份认证即用户去访问系统资源时系统要求验证用户的身份信息,身份 ...

  2. SpringSecurity - 整合JWT使用 Token 认证授权

    一.SpringSecurity 前面讲解了SpringSecurity的动态认证和动态权限角色,我们都知道在现在大多都是微服务前后端分离的模式开发,前面讲的还是基于Session的,本篇我们整合JW ...

  3. spring Cloud微服务 security+oauth2认证授权中心自定义令牌增强,并实现登录和退出

    文章目录 认证授权中心自定义令牌增强 自定义认证端点返回结果 登录逻辑调整,增强令牌返回参数 测试验证 用户微服务构建 配置类构建 相关实体类 登录 退出登录 在之前的博客我写了 SpringClou ...

  4. Spring Cloud OAuth2 JWT 微服务认证服务器得构建

    文章目录 Spring Cloud OAuth2 JWT 微服务认证服务器得构建 前言 认证服务得搭建 `AuthorizationServer` `WebSecurityConfig` `Autho ...

  5. 「springcloud 2021 系列」Spring Cloud Gateway + OAuth2 + JWT 实现统一认证与鉴权

    通过认证服务进行统一认证,然后通过网关来统一校验认证和鉴权. 将采用 Nacos 作为注册中心,Gateway 作为网关,使用 nimbus-jose-jwt JWT 库操作 JWT 令牌 理论介绍 ...

  6. 整合SpringSecurity OAuth2 JWT (附原因)一步步来如此简单

    梳理整合 SpringCloud 和 SpringSecurity OAuth2 的搭建流程,网上看了一些,感觉都很差强人意,所以决定梳理下,不多说了开鲁吧. 首先要搭建微服务基础加包版本,基于阿里系 ...

  7. SpringBoot+SpringSecurity之多模块用户认证授权同步

    在之前的文章里介绍了SpringBoot和SpringSecurity如何继承.之后我们需要考虑另外一个问题:当前微服务化也已经是大型网站的趋势,当我们的项目采用微服务化架构时,往往会出现如下情况: ...

  8. 【实战 Ids4】║ 在Swagger中调试认证授权中心

    回家的路上照顾好自己哟~ 大家好,老张已经顺利到家啦,闲的无事写两篇文章冒个泡吧,其实写的内容都是群友提出来的问题,简单的我会在群里直接提供思路,麻烦的我就写个文章说明一下吧,也是自己的一个记录作用, ...

  9. SpringSecurity-02-基于前后端分离和JWT载体的认证授权

    文章目录 1:基本概念 1:什么是认证 2:什么是会话 3.什么是授权 2:准备工作 1:分析基于jwt的登录过程 2:springsecurity原理 3:security登录认证使用 1:认证流程 ...

  10. SpringCloud 基于OAth2.0 搭建认证授权中心_02

    文章目录 一.数据库部分 1. 创建数据库 2. 初始化数据脚本 二.搭建maven父工程认证授权模块 2.1. 创建一个maven项目 2.2. 引入依赖 三.搭建认证授权模块 3.1. 创建一个子 ...

最新文章

  1. 清华姚校友陈丹琦斩获2022斯隆奖!博士论文是近十年最热之一!共计27位华人入选...
  2. ASP.NET MVC+HighCharts开发统计图表
  3. python编程从入门到精通pdf-跟老齐学Python:从入门到精通 完整版PDF[7MB]
  4. Visual Studio 2010软件安装教程
  5. 学习笔记100—强制免费下载 百度文库等网站上文档 以及客道巴巴文档 教程
  6. 第四期 | 带学斯坦福CS224n自然语言处理课+带打全球Kaggle比赛(文末重金招募老师!)...
  7. 11-3 多道批处理系统
  8. 一秒执行一次_《一秒钟》:一贯的粗旷式抓大放小,张艺谋的自命题作业总是要观众自己再做一遍...
  9. 推荐系统相关资源搜集
  10. 使用Entity Framework Core,Swagger和Postman创建ASP.NET Core Web API的分步指南
  11. css取消聚焦边框[Chrome,Safari]
  12. 用Tensorflow基于Deep Q Learning DQN 玩Flappy Bird
  13. Maven详细安装教程
  14. ZN-17A机器人光机电一体化分拣实训系统
  15. 彼得·林奇的 PEG 估值策略
  16. 苹果终于入伙 WebRTC,新一代移动 Web 应用爆发路上还有哪些坑?
  17. 计算机操作系统-运行机制、体系结构
  18. 【题解】Cutting Woods
  19. 蓝牙BLE方案|伦茨科技-智能直播补光灯方案
  20. 禁用微信浏览器字体调整的方法

热门文章

  1. 单片机 自动更改日期_自动螺丝机不可或缺之功能
  2. tensorflow安装中踩到的坑protobuf、h5py、tensorboard、werkzeug
  3. How to use neural network to realize logic 'and' and 'or'?
  4. 解决办法!!!!UnsupportedClassVersionError Unsupported major.minor version 52.0
  5. Playing Atari with Deep Reinforcement Learning 中文 讲解
  6. MapReduce如何使用多路输出
  7. 基础集合论 第一章 2 集合
  8. 使用Limelighter生成伪造代码签名
  9. c语言编写面条排序算法,腾讯PCG事业部腾讯视频面经
  10. nginx中的的ip_hash机制