zoukankan      html  css  js  c++  java
  • SpringCloud----spring security Oauth2认证解决方案

    Oauth2授权码模式

    授权码授权流程

    例举的黑马程序员网站使用微信认证的过程就是授权码模式,流程如下:

    1、客户端请求第三方授权 

    2、用户(资源拥有者)同意给客户端授权

    3、客户端获取到授权码,请求认证服务器申请令牌 

    4、认证服务器向客户端响应令牌 

    5、客户端请求资源服务器的资源,资源服务校验令牌合法性,完成授权

    6、资源服务器返回受保护资源

    申请授权码

    请求认证服务获取授权码:

    Get请求:

    localhost:40400/auth/oauth/authorize?client_id=XcWebApp&response_type=code&scop=app&redirect_uri=http://localhost

    参数列表如下:
    client_id:客户端id,和授权配置类中设置的客户端id一致。

    response_type:授权码模式固定为code

    scop:客户端范围,和授权配置类中设置的scop一致。

    redirect_uri:跳转uri,当授权码申请成功后会跳转到此地址,并在后边带上code参数(授权码)。

    数据库中有一张表,表里面存放了配置的客户端id,如果请求的参数后的client_id在数据库中不存在,是不会颁发授权码的

    请求后会跳转到登录页面

      用户名是client_id,密码是client_secret(已经加密过) 

    page10image3801088

    密码和账号输入成功后会带着授权码重定向到指定的uri下

    申请令牌

    拿到授权码后,申请令牌。Post请求
    http://localhost:40400/auth/oauth/token

    参数如下:

    grant_type:授权类型,填写authorization_code,表示授权码模式

    code:授权码,就是刚刚获取的授权码,注意:授权码只使用一次就无效了,需要重新申请。

    redirect_uri:申请授权码时的跳转url,一定和申请授权码时用的redirect_uri一致。

    此链接需要使用 http Basic认证。

    使用postMan测试,返回令牌

     

    key_store存储共钥和私钥

    资源服务授权流程

    资源服务拥有要访问的受保护资源,客户端携带令牌访问资源服务,如果令牌合法则可成功访问资源服务中的资源,如下图:

                       
    page13image1814368

    1、客户端请求认证服务申请令牌

    2、认证服务生成令牌 认证服务采用非对称加密算法,使用私钥生成令牌。

    3、客户端携带令牌访问资源服务 客户端在Http header 中添加: Authorization:Bearer 令牌。

    4、资源服务请求认证服务校验令牌的有效性 资源服务接收到令牌,使用公钥校验令牌的合法性。

    5、令牌有效,资源服务向客户端响应资源信息

     

    资源服务授权配置

    1、配置公钥

    认证服务生成令牌采用非对称加密算法,认证服务采用私钥加密生成令牌,对外向资源服务提供公钥,资源服务使 用公钥 来校验令牌的合法性。

    将公钥拷贝到 publickey.txt文件中,将此文件拷贝到资源服务工程的classpath下

    2、添加依赖

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

    4、在config包下创建ResourceServerConfig类:

    @Configuration
    @EnableResourceServer
    @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)//激活方法上的PreAuthorize注解
    public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    
    //公钥
    private static final String PUBLIC_KEY = "publickey.txt";
    
    //定义JwtTokenStore ,使用jwt令牌
    @Bean
    public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
               return new JwtTokenStore(jwtAccessTokenConverter);
           }
    //定义JJwtAccessTokenConverter,使用jwt令牌
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
    
               JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
               converter.setVerifierKey(getPubKey());
               return converter;
    } /**
    
    * 获取非对称加密公钥 Key * @return 公钥 Key
    */
    
           private String getPubKey() {
               Resource resource = new ClassPathResource(PUBLIC_KEY);
               try {
                   InputStreamReader inputStreamReader = new
       InputStreamReader(resource.getInputStream());
                   BufferedReader br = new BufferedReader(inputStreamReader);
                   return br.lines().collect(Collectors.joining("
    "));
               } catch (IOException ioe) {
                   return null;
               }
    }
    //Http安全配置,对每个到达系统的http请求链接进行校验
    @Override
    public void configure(HttpSecurity http) throws Exception {
    
    //所有请求必须认证通过http.authorizeRequests().anyRequest().authenticated();
    
    } }

    资源服务授权测试

    请求时没有携带令牌则报错:

    {
           "error": "unauthorized",
           "error_description": "Full authentication is required to access this resource"
    }
     
    使用postman携带令牌

      使用:在http header中添加 Authorization: Bearer 令牌(固定格式)

    解决swagger-ui无法访问

    修改授权配置类ResourceServerConfig的configure方法: 针对swagger-ui的请求路径进行放行:

    //Http安全配置,对每个到达系统的http请求链接进行校验
    @Override
    public void configure(HttpSecurity http) throws Exception {
    //所有请求必须认证通过 http.authorizeRequests()
    //下边的路径放行
    .antMatchers("/v2/api‐docs", "/swagger‐resources/configuration/ui",
                   "/swagger‐resources","/swagger‐resources/configuration/security",
                   "/swagger‐ui.html","/webjars/**").permitAll()
           .anyRequest().authenticated();
    }

    注意: 通过上边的配置虽然可以访问swagger-ui,但是无法进行单元测试,除非去掉认证的配置或在上边配置中添加所有请求均放行("/**")。

    Oauth2密码模式授权

    密码模式(Resource Owner Password Credentials)与授权码模式的区别是申请令牌不再使用授权码,而是直接通过用户名和密码即可申请令牌。

    测试如下:Post请求:http://localhost:40400/auth/oauth/token参数:

    grant_type:密码模式授权填写password username:账号
    password:密码
    并且此链接需要使用 http Basic认证。

    注意:当令牌没有过期时同一个用户再次申请令牌则不再颁发新令牌。

    ps:校验令牌

    Get: http://localhost:40400/auth/oauth/check_token?token=

    返回结果

    {
           "companyId": null,
           "userpic": null,
           "user_name": "mrt",
           "scope": [
    "app" ],
           "name": null,
           "utype": null,
           "id": null,
           "exp": 1531254828,
           "jti": "6a00f227‐4c30‐47dc‐a959‐c0c147806462",
           "client_id": "XcWebApp"
    }

    exp:过期时间,long类型,距离1970年的秒数(new Date().getTime()可得到当前时间距离1970年的毫秒数)。

    user_name: 用户名
    client_id:客户端Id,在oauth_client_details中配置
    scope:客户端范围,在oauth_client_details表中配置

    jti:与令牌对应的唯一标识
    companyId、userpic、name、utype、id:这些字段是本认证服务在Spring Security基础上扩展的用户身份信息

    ps:刷新令牌

    刷新令牌是当令牌快过期时重新生成一个令牌,它于授权码授权和密码授权生成令牌不同,刷新令牌不需要授权码

    也不需要账号和密码,只需要一个刷新令牌、客户端id和客户端密码。

    测试如下:
    Post:http://localhost:40400/auth/oauth/token
    参数:

    grant_type: 固定为 refresh_token

    refresh_token:刷新令牌(注意不是access_token,而是refresh_token)

    刷新令牌成功,会重新生成新的访问令牌和刷新令牌,令牌的有效期也比旧令牌长。刷新令牌通常是在令牌快过期时进行刷新。

    JWT研究

    传统校验令牌的方法

     传统授权方法的问题是用户每次请求资源服务,资源服务都需要携带令牌访问认证服务去校验令牌的合法性,并根据令牌获取用户的相关信息,性能低下。

    使用JWT

     思路是,用户认证通过会得到一个JWT令牌,JWT令牌中已经包括了用户相关的信息,客户端只需要携带JWT访问资源服务,资源服务根据事先约定的算法自行完成令牌校验,无需每次都请求认证服务完成授权。

    什么是JWT?

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

    JWT令牌的优点:
    1、jwt基于json,非常方便解析。2、可以在令牌中自定义丰富的内容,易扩展。3、通过非对称加密算法及数字签名技术,JWT防止篡改,安全性高。4、资源服务使用JWT可不依赖认证服务即可完成授权。
    缺点:
    1、JWT令牌较长,占存储空间比较大。

    JWT令牌结构

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

    • Header

    头部包括令牌的类型(即JWT)及使用的哈希算法(如HMAC SHA256或RSA) 一个例子如下:下边是Header部分的内容

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

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

    • Payload

    第二部分是负载,内容也是一个json对象,它是存放有效信息的地方,它可以存放jwt提供的现成字段,比 如:iss(签发者),exp(过期时间戳), sub(面向的用户)等,也可自定义字段。

    此部分不建议存放敏感信息,因为此部分可以解码还原原始内容。 最后将第二部分负载使用Base64Url编码,得到一个字符串就是JWT令牌的第二部分。 一个例子:

    {
             "sub": "1234567890",
             "name": "456",
             "admin": true
    }
    • Signature

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

    一个例子:

    HMACSHA256(
             base64UrlEncode(header) + "." +
             base64UrlEncode(payload),
             secret)
    

    base64UrlEncode(header):jwt令牌的第一部分。base64UrlEncode(payload):jwt令牌的第二部分。secret:签名所使用的密钥。

    普通令牌只是唯一标识了用户信息,资源服务只能通过请求认证服务来获取用户信息,而jwt令牌已经存储了用户信息,只要资源服务解析这个令牌就可以拿到用户信息

    JWT入门

    Spring Security 提供对JWT的支持,本节我们使用Spring Security 提供的JwtHelper来创建JWT令牌,校验JWT令牌等操作。

    生成私钥和公钥

    JWT令牌生成采用非对称加密算法

    1、生成密钥证书

    下边命令生成密钥证书,采用RSA 算法每个证书包含公钥和私钥

    keytool -genkeypair -alias xckey -keyalg RSA -keypass xuecheng -keystore xc.keystore -storepass xuechengkeystore

    Keytool 是一个java提供的证书管理工具

    -alias:密钥的别名
    -keyalg:使用的hash算法
    -keypass:密钥的访问密码

    -keystore:密钥库文件名,xc.keystore保存了生成的证书

    -storepass:密钥库的访问密码

    查询证书信息:

    keytool -list -keystore xc.keystore

    删除别名

    keytool -delete -alias xckey -keystore xc.keystore

    2、导出公钥

    openssl是一个加解密工具包,这里使用openssl来导出公钥信息。 安装 openssl:http://slproweb.com/products/Win32OpenSSL.html,配置openssl的path环境变量(mac自带)

    cmd进入xc.keystore文件所在目录执行如下命令

     keytool ‐list ‐rfc ‐‐keystore xc.keystore | openssl x509 ‐inform pem ‐pubkey
    

    将下面的红色框文字复制成一行,保存一个文件(publickey.txt),这个文件用于放到资源服务中

                       

    生成jwt令牌

    在认证工程创建测试类,测试jwt令牌的生成与验证。

    //生成一个jwt令牌
    @Test
    public void testCreateJwt(){
    //证书文件
    String key_location = "xc.keystore";
    //密钥库密码
    String keystore_password = "xuechengkeystore";
    //访问证书路径
    ClassPathResource resource = new ClassPathResource(key_location); //密钥工厂
    KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(resource,
    keystore_password.toCharArray()); //密钥的密码,此密码和别名要匹配
     String keypassword = "xuecheng"; //密钥别名
    String alias = "xckey"; //密钥对(密钥和公钥)
    KeyPair keyPair = keyStoreKeyFactory.getKeyPair(alias,keypassword.toCharArray()); //私钥
    RSAPrivateKey aPrivate = (RSAPrivateKey) keyPair.getPrivate();
    //定义payload信息
           Map<String, Object> tokenMap = new HashMap<>();
           tokenMap.put("id", "123");
           tokenMap.put("name", "mrt");
           tokenMap.put("roles", "r01,r02");
           tokenMap.put("ext", "1");
    //生成jwt令牌
    Jwt jwt = JwtHelper.encode(JSON.toJSONString(tokenMap), new RsaSigner(aPrivate)); //取出jwt令牌
    String token = jwt.getEncoded();
    System.out.println("token="+token);
    }

    验证jwt令牌

    //资源服务使用公钥验证jwt的合法性,并对jwt解码 
    @Test public void testVerify(){
      //jwt令牌(由上面生成) String token ="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHQiOiIxIiwicm9sZXMiOiJyMDEscjAyIiwibmFtZSI6Im1ydCIsI mlkIjoiMTIzIn0.KK7_67N5d1Dthd1PgDHMsbi0UlmjGRcm_XJUUwseJ2eZyJJWoPP2IcEZgAU3tUaaKEHUf9wSRwaDgwhrw fyIcSHbs8oy3zOQEL8j5AOjzBBs7vnRmB7DbSaQD7eJiQVJOXO1QpdmEFgjhc_IBCVTJCVWgZw60IEW1_Lg5tqaLvCiIl26K 48pJB5f‐le2zgYMzqR1L2LyTFkq39rG57VOqqSCi3dapsZQd4ctq95SJCXgGdrUDWtD52rp5o6_0uq‐ mrbRdRxkrQfsa1j8C5IW2‐T4eUmiN3f9wF9JxUK1__XC1OQkOn‐ZTBCdqwWIygDFbU7sf6KzfHJTm5vfjp6NIA";   //公钥 String publickey = "‐‐‐‐‐BEGIN PUBLIC KEY‐‐‐‐‐ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAijyxMdq4S6L1Af1rtB8SjCZHNgsQG8JTfGy55eYvzG0B/E4AudR2 prSRBvF7NYPL47scRCNPgLnvbQczBHbBug6uOr78qnWsYxHlW6Aa5dI5NsmOD4DLtSw8eX0hFyK5Fj6ScYOSFBz9cd1nNTvx 2+oIv0lJDcpQdQhsfgsEr1ntvWterZt/8r7xNN83gHYuZ6TM5MYvjQNBc5qC7Krs9wM7UoQuL+s0X6RlOib7/mcLn/lFLsLD dYQAZkSDx/6+t+1oHdMarChIPYT1sx9Dwj2j2mvFNDTKKKKAq0cv14Vrhz67Vjmz2yMJePDqUi0JYS2r0iIo7n8vN7s83v5u OQIDAQAB‐‐‐‐‐END PUBLIC KEY‐‐‐‐‐"; //校验jwt(如果jwt令牌错误,执行下面的代码会报错) Jwt jwt = JwtHelper.decodeAndVerify(token, new RsaVerifier(publickey)); //获取jwt原始内容 String claims = jwt.getClaims(); //jwt令牌 String encoded = jwt.getEncoded(); System.out.println(encoded); }

    认证接口开发

    执行流程:
    1、用户登录,请求认证服务

    2、认证服务认证通过,生成jwt令牌,将jwt令牌及相关信息写入Redis,并且将身份令牌写入cookie

    3、用户访问资源页面,带着cookie到网关

    4、网关从cookie获取token,并查询Redis校验token,如果token不存在则拒绝访问,否则放行

    5、用户退出,请求认证服务,清除redis中的token,并且删除cookie中的token使用redis存储用户的身份令牌有以下作用:

      1、实现用户退出注销功能,服务端清除令牌后,即使客户端请求携带token也是无效的。

      2、由于jwt令牌过长,不宜存储在cookie中,所以将jwt令牌存储在redis,由客户端请求服务端获取并在客户端存储。

     

    spring security 认证流程

    认证服务

    认证服务需要实现的功能如下: 
    1、登录接口 
    前端post提交账号、密码等,用户身份校验通过,生成令牌,并将令牌存储到redis。 
    将令牌写入cookie。 
    2、退出接口 
    校验当前用户的身份为合法并且为已登录状态。 
    将令牌从redis删除。 
    删除cookie中的令牌。

    spring security 自动调用UserDetailServiceImpl(它继承了UserDetailService接口)

    用户认证授权流程

     

    用户登陆首页--首页访问认证服务--认证服务访问用户中心查询用户是否存在--如果存在,将jwt令牌(长令牌,包含用户身份信息)等所有的令牌都存放到了redis,将token(短令牌(jti),用户身份令牌)存放到cookie返回给用户

    访问课程管理前端,通过前端访问用户中心--首先访问认证服务接口通过token查询jwt令牌,将令牌添加到header中--访问网关,网关通过token在redis中校验令牌是否有效--有效,请求微服务(用户中心),用户中心从header中拿到jwt令牌(每一个微服务自己都可以校验令牌合法性)

  • 相关阅读:
    Linux各目录的意义
    LinuxVIM编辑器用法
    Linux自动同步时间
    bash的基本特性
    shell-homeworkone
    shell
    笔记
    Python-1-Day
    Linux使用BIND提供域名解析服务
    Linuxautofs自动挂载服务
  • 原文地址:https://www.cnblogs.com/yanxiaoge/p/12312381.html
Copyright © 2011-2022 走看看