SpringCloud----spring security Oauth2基本使用+jwt配置

1、首先需要建表

  • 原因是如果client_id保存在数据库中,而且不自定义查询逻辑,就需要使用Oauth2为我们提供的表
  • 框架已提前为我们设计好了数据库表,但对于 MYSQL 来说,默认建表语句中主键为 Varchar(256),这超过了最大的主键长度,可改成 128,并用 BLOB 替换语句中的 LONGVARBINARY 类型
  • https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql
-- used in tests that use HSQL
create table oauth_client_details (
  client_id VARCHAR(256) PRIMARY KEY,
  resource_ids VARCHAR(256),
  client_secret VARCHAR(256),
  scope VARCHAR(256),
  authorized_grant_types VARCHAR(256),
  web_server_redirect_uri VARCHAR(256),
  authorities VARCHAR(256),
  access_token_validity INTEGER,
  refresh_token_validity INTEGER,
  additional_information VARCHAR(4096),
  autoapprove VARCHAR(256)
);

create table oauth_client_token (
  token_id VARCHAR(256),
  token LONGVARBINARY,
  authentication_id VARCHAR(256) PRIMARY KEY,
  user_name VARCHAR(256),
  client_id VARCHAR(256)
);

create table oauth_access_token (
  token_id VARCHAR(256),
  token LONGVARBINARY,
  authentication_id VARCHAR(256) PRIMARY KEY,
  user_name VARCHAR(256),
  client_id VARCHAR(256),
  authentication LONGVARBINARY,
  refresh_token VARCHAR(256)
);

create table oauth_refresh_token (
  token_id VARCHAR(256),
  token LONGVARBINARY,
  authentication LONGVARBINARY
);

create table oauth_code (
  code VARCHAR(256), authentication LONGVARBINARY
);

create table oauth_approvals (
	userId VARCHAR(256),
	clientId VARCHAR(256),
	scope VARCHAR(256),
	status VARCHAR(10),
	expiresAt TIMESTAMP,
	lastModifiedAt TIMESTAMP
);


-- customized oauth_client_details table
create table ClientDetails (
  appId VARCHAR(256) PRIMARY KEY,
  resourceIds VARCHAR(256),
  appSecret VARCHAR(256),
  scope VARCHAR(256),
  grantTypes VARCHAR(256),
  redirectUrl VARCHAR(256),
  authorities VARCHAR(256),
  access_token_validity INTEGER,
  refresh_token_validity INTEGER,
  additionalInformation VARCHAR(4096),
  autoApproveScopes VARCHAR(256)
);

 

授权服务和认证服务 

  在没有jwt令牌之前,授权服务也充当认证服务,资源服务器获取到token,会请求授权/认证服务。来校验令牌合法性。但是又了jwt令牌之后,资源服务器自己就有能力校验令牌。所以授权服务就不在充当认证服务了。

Oauth2授权码模式

1、请求授权(认证)服务,申请授权码

  • oauth/authorize:框架内部url,我们需要根据Oath2协议,传入相应的参数
  • redirect_uri:跳转uri,当授权码申请成功后会跳转到此地址,并在后边带上code参数(授权码)。
GET:localhost:40400/oauth/authorize?client_id=XcWebApp&response_type=code&scop=app&redirect_uri=http://localhost 
  • 如果我们配置的数据库(oauth_client_details)或者内存中存在XcWebApp这个client_id,就需要用户来输入用户名和密码验证身份。当然可以配置二维码,快捷登录,成功后点击

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

 

2、申请令牌

  • 拿到授权码后,Post请求申请令牌。
  • 参数:grant_type:授权类型,填写authorization_code,表示授权码模式,code:授权码,就是刚刚获取的授权码,注意:授权码只使用一次就无效了,需要重新申请。redirect_uri:申请授权码时的跳转url,一定和申请授权码时用的redirect_uri一致。
POST:http://localhost:40400/auth/oauth/token

  • 此链接需要使用 http Basic认证。传入的是client_id和密码

 

Oauth2密码模式授权

输入用户名和密码申请令牌

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

  • 需要传入clientId、clientSecret、username、password、grant_type
  • 由于基于http basic认证,clientId、clientSecret就是下面的username/password
  • grant_type:密码模式授权填写 password
POST:http://localhost:40400/auth/oauth/token
  • 并且此链接需要使用 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)

 

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

 

请求资源

  • 方式1:在headers头中,加入key:Authorization,value:Bearer [access_token]
  • 方式2:在params中,加入key:access_token,value:[access_token]
//是否可以不需要登录,访问url
HttpSecurity http
//必须登录+必须有权限
@PreAuthorize("hasAnyAuthority('course_get_baseinfo')")
  • 如果采用jwt令牌,资源服务自己校验令牌,如果是普通令牌,资源服务发送http请求,调用认证服务进行验证

 

解决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,但是无法进行单元测试,除非去掉认证的配置或在上边配置中添加所有请求均放行("/**")。

 

 

Oauth自带url

TokenEndpoint类处理
@RequestMapping(value = "/oauth/token", method=RequestMethod.POST)

  

 
 
 

JWT令牌资源服务授权配置

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("\n"));
           } 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 令牌(固定格式)

 

 

 

 

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令牌(每一个微服务自己都可以校验令牌合法性)

 

 

 

 

 

 

 

 

 

posted @ 2020-02-15 15:54  小名的同学  阅读(2871)  评论(0编辑  收藏  举报