您当前的位置:首页 > 电脑百科 > 程序开发 > 架构

OAuth2+JWT 实现权限验证

时间:2021-04-07 16:59:09  来源:今日头条  作者:Java码农之路

前言

微服务架构下统⼀认证思路主要有两种形式:

1、基于 Session 的认证⽅式在分布式的环境下,基于 session 的认证会出现⼀个问题,每个应⽤服务都需要在session中存储⽤户身份信息,通过负载均衡将本地的请求分配到另⼀个应⽤服务需要将 session 信息带过去,否则会重新认证。我们可以使⽤ Session 共享、Session 黏贴等⽅案。Session ⽅案也有缺点,⽐如基于 cookie ,移动端不能有效使⽤等

2、基于 token 的认证⽅式。基于token的认证⽅式,服务端不⽤存储认证数据,易维护扩展性强, 客户端可以把token 存在任意地⽅,并且可以实现 web 和 App 统⼀认证机制。其缺点也很明显,token 由于⾃包含信息,因此⼀般数据量较⼤,⽽且每次请求 都需要传递,因此⽐较占带宽。另外,token 的签名验签操作也会给 cpu 带来额外的处理负担。

下面我们就基于 token 的认证⽅式。采用 OAuth2 框架来实现。

OAuth2 开放授权协议/标准

OAuth(开放授权)是⼀个开放协议/标准,允许⽤户授权第三⽅应⽤访问他们存储在另外的服务提供者上的信息,⽽不需要将⽤户名和密码提供给第三⽅应⽤或分享他们数据的所有内容。允许⽤户授权第三⽅应⽤访问他们存储在另外的服务提供者上的信息,⽽不需要将⽤户名和密码提供给第三⽅应⽤或分享他们数据的所有内容。

OAuth2 协议流程图如下:

OAuth2+JWT 实现权限验证

 

1、客户端请求用户授权

2、用户确认授权

3、客户端收到授权许可后,向认证服务器申请令牌

4、认证服务器验证授权许可,向客户端返回有效令牌

5、客户端携带有效令牌访问资源服务器

6、资源服务器从认证服务器中验证有效令牌。

7、验证通过后,返回对应的资源给客户端。

什么情况下需要使⽤ OAuth2 ?

第三⽅授权登录的场景:⽐如,我们经常登录⼀些⽹站或者应⽤的时候,可以选择使⽤第三⽅授权登录的⽅式,⽐如:微信授权登录、QQ授权登录、微博授权登录等,这是典型的 OAuth2 使⽤场景。单点登录的场景:如果项⽬中有很多微服务或者公司内部有很多服务,可以专⻔做⼀个认证中⼼(充当认证平台⻆⾊),所有的服务都要到这个认证中⼼做认证,只做⼀次登录,就可以在多个授权范围内的服务中⾃由串⾏。

Spring Cloud OAuth2 + JWT 实现

Spring Cloud OAuth2 是 Spring Cloud 体系对OAuth2协议的实现,可以⽤来做多个微服务的统⼀认证(验证身份合法性)授权(验证权限)。通过向OAuth2服务(统⼀认证授权服务)发送某个类型的 grant_type 进⾏集中认证和授权,从⽽获得 access_token(访问令牌),⽽这个 token 是受其他微服务信任的。

使⽤ OAuth2 解决问题的本质是,引⼊了⼀个认证授权层,认证授权层连接了资源的拥有者,在授权层⾥⾯,资源的拥有者可以给第三⽅应⽤授权去访问我们的某些受保护资源。

搭建认证服务器

创建一个新的的模块,service-oauth-hw-9900。

依赖

pom 文件中依赖如下:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security.oauth.boot</groupId>
    <artifactId>spring-security-oauth2-autoconfigure</artifactId>
    <version>2.1.11.RELEASE</version>
</dependency>
<!--引入security对oauth2的支持-->
<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <version>2.3.4.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

配置文件

server:
  port: 9900
spring:
  application:
    name: service-oauth-hw
  zipkin:
    base-url: http://127.0.0.1:8771 # zipkin server的请求地址
    sender:
      # web 客户端将踪迹日志数据通过网络请求的方式传送到服务端,另外还有配置
      # kafka/rabbit 客户端将踪迹日志数据传递到mq进行中转
      type: web
    sleuth:
      sampler:
        # 采样率 1 代表100%全部采集 ,默认0.1 代表10% 的请求踪迹数据会被采集
        # 生产环境下,请求量非常大,没有必要所有请求的踪迹数据都采集分析,对于网络包括server端压力都是比较大的,可以配置采样率采集一定比例的请求的踪迹数据进行分析即可
        probability: 1
eureka:
  client:
    serviceUrl: # eureka server的路径
      defaultZone: http://quellanan.a:8761/eureka/,http://quellanan.b:8762/eureka/ #把 eureka 集群中的所有 url 都填写了进来,也可以只写一台,因为各个 eureka server 可以同步注册表
    instance:
      prefer-ip-address: true #使用ip注册
#分布式链路追踪
logging:
  level:
    org.springframework.web.servlet.DispatcherServlet: debug
    org.springframework.cloud.sleuth: debug

启动类

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

config

自定义一个 OauthServerConfiger。当前类为Oauth2 server的配置类(需要继承特定的父类
AuthorizationServerConfigurerAdapter)

@Configuration
@EnableAuthorizationServer  //开启认证服务器功能
public class OauthServerConfiger extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private AuthenticationManager authenticationManager;
    /**
     * 客户端详情配置,
     *  比如client_id,secret
     *  当前这个服务就如同QQ平台,拉勾网作为客户端需要qq平台进行登录授权认证等,提前需要到QQ平台注册,QQ平台会给拉勾网
     *  颁发client_id等必要参数,表明客户端是谁
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        super.configure(clients);
        // 客户端信息存储在什么地方,可以在内存中,可以在数据库里
        clients.inMemory()
                // 添加一个client配置,指定其client_id
                .withClient("quellanan")
                //指定客户端的密码/安全码
                .secret("abcdefg")
                //指定客户端所能访问资源id清单,此处的资源id是需要在具体的资源服务器上也配置一样
                .redirectUris("*")
                //认证类型/令牌颁发模式,可以配置多个在这里,但是不一定都用,具体使用哪种方式颁发token,需要客户端调用的时候传递参数指定
                .authorizedGrantTypes("password","refresh_token")
                //客户端的权限范围,此处配置为all全部即可
                .scopes("all");
    }
    /**
     * 认证服务器最终是以api接口的方式对外提供服务(校验合法性并生成令牌、校验令牌等)
     * 那么,以api接口方式对外的话,就涉及到接口的访问权限,我们需要在这里进行必要的配置
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        super.configure(security);
        // 相当于打开endpoints 访问接口的开关,这样的话后期我们能够访问该接口
        security
                // 允许客户端表单认证
                .allowFormAuthenticationForClients()
                // 开启端口/oauth/token_key的访问权限(允许)
                .tokenKeyAccess("permitAll()")
                // 开启端口/oauth/check_token的访问权限(允许)
                .checkTokenAccess("permitAll()");
    }
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        super.configure(endpoints);
        endpoints
                // 指定token的存储方法
                .tokenStore(tokenStore())
                // token服务的一个描述,可以认为是token生成细节的描述,比如有效时间多少等
                .tokenServices(authorizationServerTokenServices())
                // 指定认证管理器,随后注入一个到当前类使用即可
                .authenticationManager(authenticationManager)
                .allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST);
    }
    /*
        该方法用于创建tokenStore对象(令牌存储对象)
        token以什么形式存储
     */
    public TokenStore tokenStore(){
        return new InMemoryTokenStore();
        // 使用jwt令牌
        //return new JwtTokenStore(jwtAccessTokenConverter());
    }
    /**
     * 该方法用户获取一个token服务对象(该对象描述了token有效期等信息)
     */
    public AuthorizationServerTokenServices authorizationServerTokenServices() {
        // 使用默认实现
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setSupportRefreshToken(true); // 是否开启令牌刷新
        defaultTokenServices.setTokenStore(tokenStore());
        // 针对jwt令牌的添加
        //defaultTokenServices.setTokenEnhancer(jwtAccessTokenConverter());
        // 设置令牌有效时间(一般设置为2个小时)
        defaultTokenServices.setAccessTokenValiditySeconds(20); // access_token就是我们请求资源需要携带的令牌
        // 设置刷新令牌的有效时间
        defaultTokenServices.setRefreshTokenValiditySeconds(259200); // 3天
        return defaultTokenServices;
    }
}

关于三个 configure ⽅法

configure(
ClientDetailsServiceConfifigurer clients):⽤来配置客户端详情服务(ClientDetailsService),客户端详情信息在 这⾥进⾏初始化,你能够把客户端详情信息写死在这⾥或者是通过数据库来存储调取详情信息

confifigure(
AuthorizationServerEndpointsConfifigurer endpoints):⽤来配置令牌(token)的访问端点和令牌服务(token services)

confifigure(
AuthorizationServerSecurityConfifigurer oauthServer):⽤来配置令牌端点的安全约束.

关于 TokenStore

InMemoryTokenStore默认采⽤,它可以完美的⼯作在单服务器上(即访问并发量 压⼒不⼤的情况下,并且它在失败的时候不会进⾏备份),⼤多数的项⽬都可以使⽤这个版本的实现来进⾏ 尝试,你可以在开发的时候使⽤它来进⾏管理,因为不会被保存到磁盘中,所以更易于调试。

JdbcTokenStore这是⼀个基于JDBC的实现版本,令牌会被保存进关系型数据库。使⽤这个版本的实现时, 你可以在不同的服务器之间共享令牌信息,使⽤这个版本的时候请注意把"springjdbc"这个依赖加⼊到你的 classpath当中。JwtTokenStore 这个版本的全称是 JSON Web Token(JWT),它可以把令牌相关的数据进⾏编码(因此对于后端服务来说,它不需要进⾏存储,这将是⼀个重⼤优势),缺点就是这个令牌占⽤的空间会⽐较⼤,如果你加⼊了⽐较多⽤户凭证信息,JwtTokenStore 不会保存任何数据。

然后再自定义有一个配置类,主要处理用户名和密码的校验等事宜。

@Configuration
public class SecurityConfiger extends WebSecurityConfigurerAdapter {
    @Autowired
    private PasswordEncoder passwordEncoder;
    //@Autowired
    //private JdbcUserDetailsService jdbcUserDetailsService;
    /**
     * 注册一个认证管理器对象到容器
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    /**
     * 密码编码对象(密码不进行加密处理)
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
    /**
     * 处理用户名和密码验证事宜
     * 1)客户端传递username和password参数到认证服务器
     * 2)一般来说,username和password会存储在数据库中的用户表中
     * 3)根据用户表中数据,验证当前传递过来的用户信息的合法性
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 在这个方法中就可以去关联数据库了,当前我们先把用户信息配置在内存中
        // 实例化一个用户对象(相当于数据表中的一条用户记录)
        UserDetails user = new User("admin","123456",new ArrayList<>());
        auth.inMemoryAuthentication()
                .withUser(user).passwordEncoder(passwordEncoder);
        //auth.userDetailsService(jdbcUserDetailsService).passwordEncoder(passwordEncoder);
    }
}

JWT 改造统⼀认证授权中⼼的令牌存储机制

JWT 令牌介绍

通过上边的测试我们发现,当资源服务和授权服务不在⼀起时资源服务使⽤RemoteTokenServices 远程请求授权 服务验证token,如果访问量较⼤将会影响系统的性能。

解决上边问题: 令牌采⽤JWT格式即可解决上边的问题,⽤户认证通过会得到⼀个JWT令牌,JWT令牌中已经包括了⽤户相关的信 息,客户端只需要携带JWT访问资源服务,资源服务根据事先约定的算法⾃⾏完成令牌校验,⽆需每次都请求认证 服务完成授权。

什么是JWT?

JSON Web Token(JWT)是⼀个开放的⾏业标准(RFC 7519),它定义了⼀种简介的、⾃包含的协议格式,⽤于 在通信双⽅传递json对象,传递的信息经过数字签名可以被验证和信任。JWT可以使⽤Hmac算法或使⽤RSA的公 钥/私钥对来签名,防⽌被篡改。

JWT令牌结构

JWT 令牌由三部分组成,每部分中间使⽤点(.)分隔,⽐如:xxxxx.yyyyy.zzzzz

Header。头部包括令牌的类型(即JWT)及使⽤的哈希算法(如HMAC SHA256或RSA),例如

{
 "alg": "HS256",
 "typ": "JWT"
}

将上边的内容使⽤Base64Url编码,得到⼀个字符串就是JWT令牌的第⼀部分。

Payload。第⼆部分是负载,内容也是⼀个json对象,它是存放有效信息的地⽅,它可以存放jwt提供的现成字段,⽐ 如:iss(签发者),exp(过期时间戳), sub(⾯向的⽤户)等,也可⾃定义字段。 此部分不建议存放敏感信息,因为此部分可以解码还原原始内容。 最后将第⼆部分负载使⽤Base64Url编码,得到⼀个字符串就是JWT令牌的第⼆部分。 ⼀个例⼦:

{
 "sub": "1234567890",
 "name": "John Doe",
 "iat": 1516239022
}

Signature。第三部分是签名,此部分⽤于防⽌jwt内容被篡改。 这个部分使⽤base64url将前两部分进⾏编码,编码后使⽤点(.)连接组成字符串,最后使⽤header中声明 签名算法进⾏签名。

HMACSHA256(
 base64UrlEncode(header) + "." +
 base64UrlEncode(payload),
 secret)
  1. base64UrlEncode(header):jwt令牌的第⼀部分。
  2. base64UrlEncode(payload):jwt令牌的第⼆部分。

secret:签名所使⽤的密钥。

认证服务器端JWT改造(改造主配置类)

/*
该方法用于创建tokenStore对象(令牌存储对象)
token以什么形式存储
*/
public TokenStore tokenStore(){
    //return new InMemoryTokenStore();
    // 使用jwt令牌
    return new JwtTokenStore(jwtAccessTokenConverter());
}
/**
* 返回jwt令牌转换器(帮助我们生成jwt令牌的)
* 在这里,我们可以把签名密钥传递进去给转换器对象
* @return
*/
public JwtAccessTokenConverter jwtAccessTokenConverter() {
    JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
    jwtAccessTokenConverter.setSigningKey(sign_key);  // 签名密钥
    jwtAccessTokenConverter.setVerifier(new MacSigner(sign_key));  // 验证时使用的密钥,和签名密钥保持一致
    jwtAccessTokenConverter.setAccessTokenConverter(lagouAccessTokenConvertor);
return jwtAccessTokenConverter;
}

修改 JWT 令牌服务⽅法

/**
 * 该方法用户获取一个token服务对象(该对象描述了token有效期等信息)
 */
public AuthorizationServerTokenServices authorizationServerTokenServices() {
    // 使用默认实现
    DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
    defaultTokenServices.setSupportRefreshToken(true); // 是否开启令牌刷新
    defaultTokenServices.setTokenStore(tokenStore());
    // 针对jwt令牌的添加
    defaultTokenServices.setTokenEnhancer(jwtAccessTokenConverter());
    // 设置令牌有效时间(一般设置为2个小时)
    defaultTokenServices.setAccessTokenValiditySeconds(20); // access_token就是我们请求资源需要携带的令牌
    // 设置刷新令牌的有效时间
    defaultTokenServices.setRefreshTokenValiditySeconds(259200); // 3天
    return defaultTokenServices;
}

总结

我们在实际工作中,token 鉴权的方式是很常见的现在,这一套解决方案也可以直接使用到项目中,小伙伴们赶紧学习起来吧。



Tags:OAuth2   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
最开始接触 OAuth2.0 的时候,经常将它和 SSO单点登录搞混。后来因为工作需要,在项目中实现了一套SSO,通过对SSO的逐渐了解,也把它和OAuth2.0区分开了。所以当时自己也整理了一篇...【详细内容】
2021-09-16  Tags: OAuth2  点击:(68)  评论:(0)  加入收藏
前言微服务架构下统⼀认证思路主要有两种形式:1、基于 Session 的认证⽅式在分布式的环境下,基于 session 的认证会出现⼀个问题,每个应⽤服务都需要在session中存储⽤户身份信...【详细内容】
2021-04-07  Tags: OAuth2  点击:(252)  评论:(0)  加入收藏
介绍OAuth(开放授权)是一个开放标准,允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方应用或分享他们数据的所有内容。OAuth...【详细内容】
2020-08-18  Tags: OAuth2  点击:(65)  评论:(0)  加入收藏
从事互联网开发的同学们应该或多或少听说过 OAuth2.0 协议,例如使用微信或支付宝账户登录第三方App,这是 OAuth2.0 最为开发人员所熟知的一个用途,但是围绕着 OAuth2.0 协议其...【详细内容】
2020-07-12  Tags: OAuth2  点击:(102)  评论:(0)  加入收藏
序言安全性是暴露由许多微服务组成的公共访问API时要考虑的最重要的一个方面。Spring有一些有趣的功能和框架,使我们的微服务安全配置更容易。在本文中,我将向您展示如何使用S...【详细内容】
2019-08-28  Tags: OAuth2  点击:(351)  评论:(0)  加入收藏
一、什么是OAuth协议OAuth 协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是 OAuth的授权不会使第三方触及到用户的帐号信息(如用户...【详细内容】
2019-08-01  Tags: OAuth2  点击:(269)  评论:(0)  加入收藏
▌简易百科推荐
为了构建高并发、高可用的系统架构,压测、容量预估必不可少,在发现系统瓶颈后,需要有针对性地扩容、优化。结合楼主的经验和知识,本文做一个简单的总结,欢迎探讨。1、QPS保障目标...【详细内容】
2021-12-27  大数据架构师    Tags:架构   点击:(3)  评论:(0)  加入收藏
前言 单片机开发中,我们往往首先接触裸机系统,然后到RTOS,那么它们的软件架构是什么?这是我们开发人员必须认真考虑的问题。在实际项目中,首先选择软件架构是非常重要的,接下来我...【详细内容】
2021-12-23  正点原子原子哥    Tags:架构   点击:(7)  评论:(0)  加入收藏
现有数据架构难以支撑现代化应用的实现。 随着云计算产业的快速崛起,带动着各行各业开始自己的基于云的业务创新和信息架构现代化,云计算的可靠性、灵活性、按需计费的高性价...【详细内容】
2021-12-22    CSDN  Tags:数据架构   点击:(10)  评论:(0)  加入收藏
▶ 企业级项目结构封装释义 如果你刚毕业,作为Java新手程序员进入一家企业,拿到代码之后,你有什么感觉呢?如果你没有听过多模块、分布式这类的概念,那么多半会傻眼。为什么一个项...【详细内容】
2021-12-20  蜗牛学苑    Tags:微服务   点击:(8)  评论:(0)  加入收藏
我是一名程序员关注我们吧,我们会多多分享技术和资源。进来的朋友,可以多了解下青锋的产品,已开源多个产品的架构版本。Thymeleaf版(开源)1、采用技术: springboot、layui、Thymel...【详细内容】
2021-12-14  青锋爱编程    Tags:后台架构   点击:(20)  评论:(0)  加入收藏
在了解连接池之前,我们需要对长、短链接建立初步认识。我们都知道,网络通信大部分都是基于TCP/IP协议,数据传输之前,双方通过“三次握手”建立连接,当数据传输完成之后,又通过“四次挥手”释放连接,以下是“三次握手”与“四...【详细内容】
2021-12-14  架构即人生    Tags:连接池   点击:(16)  评论:(0)  加入收藏
随着移动互联网技术的快速发展,在新业务、新领域、新场景的驱动下,基于传统大型机的服务部署方式,不仅难以适应快速增长的业务需求,而且持续耗费高昂的成本,从而使得各大生产厂商...【详细内容】
2021-12-08  架构驿站    Tags:分布式系统   点击:(23)  评论:(0)  加入收藏
本系列为 Netty 学习笔记,本篇介绍总结Java NIO 网络编程。Netty 作为一个异步的、事件驱动的网络应用程序框架,也是基于NIO的客户、服务器端的编程框架。其对 Java NIO 底层...【详细内容】
2021-12-07  大数据架构师    Tags:Netty   点击:(16)  评论:(0)  加入收藏
前面谈过很多关于数字化转型,云原生,微服务方面的文章。虽然自己一直做大集团的SOA集成平台咨询规划和建设项目,但是当前传统企业数字化转型,国产化和自主可控,云原生,微服务是不...【详细内容】
2021-12-06  人月聊IT    Tags:架构   点击:(23)  评论:(0)  加入收藏
微服务看似是完美的解决方案。从理论上来说,微服务提高了开发速度,而且还可以单独扩展应用的某个部分。但实际上,微服务带有一定的隐形成本。我认为,没有亲自动手构建微服务的经历,就无法真正了解其复杂性。...【详细内容】
2021-11-26  GreekDataGuy  CSDN  Tags:单体应用   点击:(35)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条