资源文件简介
文章参考自 https://projects.spring.io/spring-security-oauth/docs/oauth2.html
一个资源服务(可以和授权服务在同一个应用中,当然也可以分离开成为两个不同的应用程序)提供一些受token令牌保护的资源,Spring OAuth提供者是通过Spring Security authentication filter 即验证过滤器来实现的保护,你可以通过 @EnableResourceServer 注解到一个 @Configuration 配置类上,并且必须使用 ResourceServerConfigurer 这个配置对象来进行配置(可以选择继承自 ResourceServerConfigurerAdapter 然后覆写其中的方法,参数就是这个对象的实例),下面是一些可以配置的属性:
- tokenServices:ResourceServerTokenServices 类的实例,用来实现令牌服务。
- resourceId:这个资源服务的ID,这个属性是可选的,但是推荐设置并在授权服务中进行验证。
- 其他的拓展属性例如 tokenExtractor 令牌提取器用来提取请求中的令牌。
- 请求匹配器,用来设置需要进行保护的资源路径,默认的情况下是受保护资源服务的全部路径。
- 受保护资源的访问规则,默认的规则是简单的身份验证(plain authenticated)。
- 其他的自定义权限保护规则通过 HttpSecurity 来进行配置。
@EnableResourceServer 注解自动增加了一个类型为 OAuth2AuthenticationProcessingFilter 的过滤器链,
在XML配置中,使用 标签元素并指定id为一个servlet过滤器就能够手动增加一个标准的过滤器链。
ResourceServerTokenServices 是组成授权服务的另一半,如果你的授权服务和资源服务在同一个应用程序上的话,你可以使用 DefaultTokenServices ,这样的话,你就不用考虑关于实现所有必要的接口的一致性问题,这通常是很困难的。如果你的资源服务器是分离开的,那么你就必须要确保能够有匹配授权服务提供的 ResourceServerTokenServices,它知道如何对令牌进行解码。
在授权服务器上,你通常可以使用 DefaultTokenServices 并且选择一些主要的表达式通过 TokenStore(后端存储或者本地编码)。
RemoteTokenServices 可以作为一个替代,它将允许资源服务器通过HTTP请求来解码令牌(也就是授权服务的 /oauth/check_token 端点)。如果你的资源服务没有太大的访问量的话,那么使用RemoteTokenServices 将会很方便(所有受保护的资源请求都将请求一次授权服务用以检验token值),或者你可以通过缓存来保存每一个token验证的结果。
使用授权服务的 /oauth/check_token 端点你需要将这个端点暴露出去,以便资源服务可以进行访问,这在咱们授权服务配置中已经提到了,下面是一个例子:
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')")
.checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')");
}
在这个例子中,我们配置了 /oauth/check_token 和 /oauth/token_key 这两个端点(受信任的资源服务能够获取到公有密匙,这是为了验证JWT令牌)。这两个端点使用了HTTP Basic Authentication 即HTTP基本身份验证,使用 client_credentials 授权模式可以做到这一点。
配置OAuth-Aware表达式处理器(OAuth-Aware Expression Handler):
你也许希望使用 Spring Security’s expression-based access control 来获得一些优势,一个表达式处理器会被注册到默认的 @EnableResourceServer 配置中,这个表达式包含了 #oauth2.clientHasRole,#oauth2.clientHasAnyRole 以及 #oauth2.denyClient 所提供的方法来帮助你使用权限角色相关的功能(在 OAuth2SecurityExpressionMethods 中有完整的列表)。
在XML配置中你可以注册一个 OAuth-Aware 表达式处理器即 元素标签到 常规的 安全配置上。
资源文件实战
使用之前的授权服务器: https://blog.csdn.net/liaomin416100569/article/details/88529127#_134
授权服务器改造
由于之前在授权服务器使用了jwtstore,使用资源服务器验证时发现JwtHelper类的验证token
有个SignatureVerifier发现如何不使用RSA(公私密钥)verifier永远是空,永远是400 null错误
public static Jwt decodeAndVerify(String token, SignatureVerifier verifier) {
Jwt jwt = decode(token);
jwt.verifySignature(verifier);
return jwt;
}
生成公私密钥(注意一定要添加–keyalg RSA 默认是DSA哦,JWT只支持RSA)
C:\Users\Administrator>keytool -genkeypair --keyalg RSA -keystore c:/a.keystore -alias test1
输入密钥库口令:
您的名字与姓氏是什么?
[Unknown]: a
您的组织单位名称是什么?
[Unknown]: a
您的组织名称是什么?
[Unknown]: a
您所在的城市或区域名称是什么?
[Unknown]: a
您所在的省/市/自治区名称是什么?
[Unknown]: a
该单位的双字母国家/地区代码是什么?
[Unknown]: a
CN=a, OU=a, O=a, L=a, ST=a, C=a是否正确?
[否]: y
输入 <test1> 的密钥口令
(如果和密钥库口令相同, 按回车):
将生成的c:/a.keystore拷贝到授权项目的src/main/resources目录
改造授权服务器配置方法
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
super.configure(endpoints);
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
//一定要设置对称的SigningKey 用于验证之后的token是否有效的
jwtAccessTokenConverter.setSigningKey("123456");
KeyStore keyStore = KeyStore.getInstance("JCEKS");
keyStore.load(OAuth2AuthorizationServerConfig.class.getResourceAsStream("/a.keystore"),"123456".toCharArray());
PublicKey publicKey = keyStore.getCertificate("test1").getPublicKey();
PrivateKey privateKey = (PrivateKey)keyStore.getKey("test1", "123456".toCharArray());
KeyPair kp=new KeyPair(publicKey,privateKey);
jwtAccessTokenConverter.setKeyPair(kp);
JwtTokenStore jwtTokenStore = new JwtTokenStore(jwtAccessTokenConverter);
endpoints.pathMapping("/oauth/confirm_access ","/extenal/oauth/confirm_access")
.tokenStore(jwtTokenStore)
.tokenEnhancer(jwtAccessTokenConverter)
//添加额外信息到token中,注意jwtAccessTokenConverter不能设置额外的tokenEnhancer否则无法生成jwt的token注释掉
// .tokenEnhancer(new TokenEnhancer() {
// @Override
// public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
// DefaultOAuth2AccessToken doat= (DefaultOAuth2AccessToken) oAuth2AccessToken;
// Map<String, Object> additionalInfo = new HashMap<>();
// additionalInfo.put("myname", "jiaozi");
// doat.setAdditionalInformation(additionalInfo);
// return doat;
// }
// })
;
}
资源服务器改造
新建maven项目
添加依赖(和资源服务器一致)
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.8.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>1.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>nekohtml</groupId>
<artifactId>nekohtml</artifactId>
<version>1.9.6.2</version>
</dependency>
</dependencies>
添加资源服务器配置类OAuth2ResourceServerConfig
@Configuration
@EnableResourceServer
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
super.configure(http);
}
public RestTemplate restTemplate() {
//httpRequestFactory()
RestTemplate restTemplate = new RestTemplate();
List<HttpMessageConverter<?>> converters = restTemplate.getMessageConverters();
for (HttpMessageConverter<?> converter : converters) {
if (converter instanceof MappingJackson2HttpMessageConverter) {
MappingJackson2HttpMessageConverter jsonConverter = (MappingJackson2HttpMessageConverter) converter;
jsonConverter.setObjectMapper(new ObjectMapper());
List<MediaType> supportedMediaTypes=new ArrayList<>();
supportedMediaTypes.add(new MediaType("application", "json", MappingJackson2HttpMessageConverter.DEFAULT_CHARSET));
supportedMediaTypes.add(new MediaType("text", "javascript", MappingJackson2HttpMessageConverter.DEFAULT_CHARSET));
jsonConverter.setSupportedMediaTypes(supportedMediaTypes);
}
}
return restTemplate;
}
/**
因为资源服务器和授权服务器分离所以使用RemoteTokenServices
*/
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
super.configure(resources);
RemoteTokenServices remoteTokenServices=new RemoteTokenServices();
remoteTokenServices.setCheckTokenEndpointUrl("http://localhost:8889/oauth/check_token");//指定授权服务器检查token的地址
remoteTokenServices.setClientId("client");
remoteTokenServices.setClientSecret("secret");
remoteTokenServices.setRestTemplate(restTemplate());
remoteTokenServices.setAccessTokenConverter(new JwtAccessTokenConverter());
resources.tokenServices(remoteTokenServices);
}
}
配置application.properties
server.port=8887
spring.thymeleaf.cache=false
spring.thymeleaf.mode=LEGACYHTML5
添加启动类
@SpringBootApplication
public class ResourceServerMain {
public static void main(String[] args) {
SpringApplication.run(ResourceServerMain.class);
}
}
添加一个控制层的资源
@Controller
public class TestController {
@ResponseBody
@GetMapping("/test")
public String test() {
return "hello";
}
}
开始测试(前面两步过程参考授权服务器文章)
- 访问认证服务器获取授权码
localhost:8889/oauth/authorize?client_id=client&response_type=code&redirect_uri=http://www.baidu.com
拷贝授权码 假设是Ce6BxM - 通过授权码获取token
{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NTI1OTk1MDEsInVzZXJfbmFtZSI6InRlc3QiLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiNjQxNzA0MTEtYTQ3Ni00NzQ5LWIzZTItOTM2NzA0NmY5MjAzIiwiY2xpZW50X2lkIjoiY2xpZW50Iiwic2NvcGUiOlsiYWxsIl19.XERRnoc-NVXJ2H6wweDK2hl9Wf7gI48NHY0aMXh2g16Hctwv60-wn41FcQnOVuSxChbBe7oe5kaXQdq7SjbTMbjAD0VPu6B4X18IsTgJ5BP-tGruhWdtxcqCJ_Gg8HRkI-62F_RO7n-B1zkf-ZmvSPO3chvBL7xiH8S0lE0c1b5FXMMIFtoqvSPVPNAt9UDo6p4JiGWwKq9Podo1bH9FxOxoHrVBYb03IRn_ASjd0Vx0iQZVG-J6VTDDUfHcHPc1HwPgaK7aEoaE-9WucsgtXDaa-C69PcU-XHPwxfKDAYrgTIAJ1kux0DLuMjWavALho1BxlVeWImFe3b0WPhKooA","token_type":"bearer","expires_in":43199,"scope":"all","jti":"64170411-a476-4749-b3e2-9367046f9203"}
- 通过授权码访问/test
注意访问的过程是在访问的请求头 加上
Authorization: Bearer 你的token
我图里面用的post请求
{"timestamp":"2019-03-14T09:40:59.035+0000","status":405,"error":"Method Not Allowed","message":"Request method 'POST' not supported","path":"/test"}
我的控制层是get请求使用get请求成功输出hello
你也可以直接在浏览器输入check_token来检查token是否正确