配置一个授权服务,需要考虑 授权类型(GrantType)、不同授权类型为客户端(Client)提供了不同的获取令牌(Token)方式,每一个客户端(Client)都能够通过明确的配置以及权限来实现不同的授权访问机制,也就是说如果你提供了一个 “client_credentials” 授权方式,并不意味着其它客户端就要采用这种方式来授权
使用 @EnableAuthorizationServer 来配置授权服务机制,并继承 AuthorizationServerConfigurerAdapter 该类重写 configure 方法定义授权服务器策略
读和写令牌所用的tokenService不同
ResourceServerTokenServices 接口定义了令牌加载、读取方法AuthorizationServerTokenServices 接口定义了令牌的创建、获取、刷新方法ConsumerTokenServices 定义了令牌的撤销方法(删除)DefaultTokenServices 实现了上述三个接口,它包含了一些令牌业务的实现,如创建令牌、读取令牌、刷新令牌、获取客户端ID。默认的当尝试创建一个令牌时,是使用 UUID 随机值进行填充的,除了持久化令牌是委托一个 TokenStore 接口实现以外,这个类几乎帮你做了所有事情而 TokenStore 接口也有一些实现: InMemoryTokenStore:默认采用该实现,将令牌信息保存在内存中,易于调试JdbcTokenStore:令牌会被保存近关系型数据库,可以在不同服务器之间共享令牌JwtTokenStore:使用 JWT 方式保存令牌,它不需要进行存储,但是它撤销一个已经授权令牌会非常困难,所以通常用来处理一个生命周期较短的令牌以及撤销刷新令牌配置授权服务类,创建一个类继承 AuthorizationServerConfigurerAdapter 并添加 @EnableAuthorizationServer 注解,添加客户端信息
@Configuration @EnableAuthorizationServer public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { //添加客户端信息 clients.inMemory() // 使用in-memory存储客户端信息 .withClient("client") // client_id .secret("secret") // client_secret .authorizedGrantTypes("authorization_code") // 该client允许的授权类型 .scopes("app"); // 允许的授权范围 } }修改配置文件,设置 Security 密码为 password,用户名为 root,相当于一个资源拥有者(用户)的账号密码
security: user: name: root password: 1234 server: port: 8081通过浏览器模拟客户端访问授权端点 /oauth/authorize
#(该步骤为**授权码模式中的A**),需要附上客户端申请认证的参数(**A步骤中所包含的参数**) localhost:8081/oauth/authorize?client_id=client&response_type=code&redirect_uri=http://www.baidu.com进入用户登陆页面(该步骤为授权码模式中的B),第三方验证页面,一般为表单验证
输入 root 1234 登陆后会进入下面页面,询问用户是否授权客户端(该步骤为授权码模式中的C),confirm_access,一般可以跳过此验证
勾选授权后点击按钮会跳转到百度
#(**A步骤中包含的参数定义了重定向URL**),并在 URL 中包含一个授权码 https://www.baidu.com/?code=mhlA24客户端拿到授权码后,附上先前设置的重定向 URL 向服务器申请令牌
# (该步骤为**授权码模式中的D**),通过令牌端点 /oauth/token # 使用 CURL 工具发送 POST 命令,授权码模式不需要 client_sercet,因此该值可以为任意值 curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'grant_type=authorization_code&code=Li4NZo&redirect_uri=http://www.baidu.com' "http://client:secret@localhost:8081/oauth/token"返回令牌如下
{"access_token":"d0e2f362-3bfd-43bb-a6ca-b6cb1b8ea9ee","token_type":"bearer","expires_in":43199,"scope":"app"}编写 @Configuration 类继承 AuthorizationServerConfigurerAdapter
@Configuration @EnableAuthorizationServer public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Autowired private DataSource dataSource; @Autowired private TokenStore tokenStore; private ClientDetailsService clientDetailsService; @Bean // 声明TokenStore实现 public TokenStore tokenStore() { return new JdbcTokenStore(dataSource); } @Bean // 声明 ClientDetails实现 public ClientDetailsService clientDetails() { return new JdbcClientDetailsService(dataSource); } @Override // 配置框架应用上述实现 public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager); endpoints.tokenStore(tokenStore); // 配置TokenServices参数 DefaultTokenServices tokenServices = new DefaultTokenServices(); tokenServices.setTokenStore(endpoints.getTokenStore()); tokenServices.setSupportRefreshToken(false); tokenServices.setClientDetailsService(endpoints.getClientDetailsService()); tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer()); tokenServices.setAccessTokenValiditySeconds( (int) TimeUnit.DAYS.toSeconds(30)); // 30天 endpoints.tokenServices(tokenServices); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.withClientDetails(clientDetailsService); } }修改配置文件,并引入 MYSQL 和 JDBC 依赖库
spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/client?useUnicode=yes&characterEncoding=UTF-8 username: root password: 123456ly <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency>往数据库 oauth_client_details 表添加客户端信息
对称加密,对称加密表示认证服务端和客户端的共用一个密钥
@Configuration @EnableAuthorizationServer public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Autowired private TokenStore tokenStore; //告诉Spring Security Token的生成方式 @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.tokenStore(tokenStore) .accessTokenConverter(jwtAccessTokenConverter()) .authenticationManager(authenticationManager); } @Bean public TokenStore tokenStore() { return new JwtTokenStore(jwtAccessTokenConverter()); } //使用同一个密钥来编码 JWT 中的 OAuth2 令牌 @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setSigningKey("123"); return converter; } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() // 使用in-memory存储客户端信息 .withClient("client") // client_id .secret("secret") // client_secret .authorizedGrantTypes("authorization_code") // 该client允许的授权类型 .scopes("app") // 允许的授权范围 .autoApprove(true); //登录后绕过批准询问(/oauth/confirm_access) } }使用不对称的密钥来签署令牌
生成 JKS Java KeyStore 文件
keytool -genkeypair -alias mytest -keyalg RSA -keypass mypass -keystore mytest.jks -storepass mypass导出公钥
keytool -list -rfc --keystore mytest.jks | openssl x509 -inform pem -pubkey将公钥保存为 pubkey.txt,将 mytest.jks()授权服务器) 和 pubkey.txt(资源服务器) 放到 resource 目录下
-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhAF1qpL+8On3rF2M77lR +l3WXKpGXIc2SwIXHwQvml/4SG7fJcupYVOkiaXj4f8g1e7qQCU4VJGvC/gGJ7sW fn+L+QKVaRhs9HuLsTzHcTVl2h5BeawzZoOi+bzQncLclhoMYXQJJ5fULnadRbKN HO7WyvrvYCANhCmdDKsDMDKxHTV9ViCIDpbyvdtjgT1fYLu66xZhubSHPowXXO15 LGDkROF0onqc8j4V29qy5iSnx8I9UIMEgrRpd6raJftlAeLXFa7BYlE2hf7cL+oG hY+q4S8CjHRuiDfebKFC1FJA3v3G9p9K4slrHlovxoVfe6QdduD8repoH07jWULu qQIDAQAB -----END PUBLIC KEY-----验证服务器配置
@Configuration @EnableAuthorizationServer public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Autowired private TokenStore tokenStore; //告诉Spring Security Token的生成方式 @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.tokenStore(tokenStore) .accessTokenConverter(jwtAccessTokenConverter()) .authenticationManager(authenticationManager); } @Override public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { oauthServer //允许所有资源服务器访问公钥端点(/oauth/token_key) //只允许验证用户访问令牌解析端点(/oauth/check_token) .tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()") // 允许客户端发送表单来进行权限认证来获取令牌 .allowFormAuthenticationForClients(); } @Bean public TokenStore tokenStore() { return new JwtTokenStore(jwtAccessTokenConverter()); } @Bean //使用私钥编码 JWT 中的 OAuth2 令牌 public JwtAccessTokenConverter jwtAccessTokenConverter() { final JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("mytest.jks"), "mypass".toCharArray()); converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mytest")); return converter; } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() // 测试用,将客户端信息存储在内存中 .withClient("client") // client_id .secret("secret") // client_secret .authorizedGrantTypes("authorization_code") // 该client允许的授权类型 .scopes("app") // 允许的授权范围 .autoApprove(true); //登录后绕过批准询问(/oauth/confirm_access) } }自定义令牌声明,添加额外的属性
添加一个额外的字段 "组织" 到令牌中
public class CustomTokenEnhancer implements TokenEnhancer { @Override public OAuth2AccessToken enhance( OAuth2AccessToken accessToken, OAuth2Authentication authentication) { Map<String, Object> additionalInfo = new HashMap<>(); additionalInfo.put("organization", authentication.getName() + randomAlphabetic(4)); ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo); return accessToken; } }将把它连接到我们的授权服务器配置
@Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain(); tokenEnhancerChain.setTokenEnhancers( Arrays.asList(tokenEnhancer(), accessTokenConverter())); endpoints.tokenStore(tokenStore()) .tokenEnhancer(tokenEnhancerChain) .authenticationManager(authenticationManager); } @Bean public TokenEnhancer tokenEnhancer() { return new CustomTokenEnhancer(); }此时令牌如下
{ "user_name": "john", "scope": [ "foo", "read", "write" ], "organization": "johnIiCh", "exp": 1458126622, "authorities": [ "ROLE_USER" ], "jti": "e0ad1ef3-a8a5-4eef-998d-00b26bc2c53f", "client_id": "fooClientIdPassword" }测试
启动授权服务器、启动资源服务器
访问授权服务器 /oauth/authorize 端点获取授权码 code=vT4fY0
localhost:8081/oauth/authorize?client_id=client&response_type=code&redirect_uri=http://www.baidu.com访问授权服务器 /oauth/token 端点获取访问令牌
WX20180112-111436@2x.png
访问资源服务器受保护的资源,附上令牌在请求头,**需加上 Bearer **
作者:林塬 链接:https://www.jianshu.com/p/227f7e7503cb 來源:简书 简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。