【项目需求】
需要对外提供一套关于订单操作的接口,因为旧系统框架,并不支持Restful风格,考虑到对外提供的一套接口肯定不能像内部使用一样,让人一看就看出了不规范,所以,独立出来了一个专门对外的api接口服务。既然对外,当然需要一定的限制,从而保证接口的安全性。从这方面考虑,这一服务的框架就决定使用Spring boot 集成OAuth2.0,使用的是客户端模式。
【框架搭建】
- maven依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.1</version>
</dependency>
</dependencies>
- 配置资源服务器
@Configuration
@EnableResourceServer
public class MyResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId("goods").stateless(true);
}
@Override
public void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
// Since we want the protected resources to be accessible in the UI as well we need
// session creation to be allowed (it's disabled by default in 2.0.6)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.requestMatchers().anyRequest()
.and()
.anonymous()
.and()
.authorizeRequests()
//配置order访问控制,必须认证过后才可以访问
.antMatchers("/orders/**").authenticated();
// @formatter:on
}
}
- 配置授权服务器
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private MyClientDetailsService myClientDetailsService;
@Autowired
private MyPasswordEncoder myPasswordEncoder;
@Autowired
private MyExceptionTranslator myExceptionTranslator;
/**
* 自定义客户端模式认证 MyClientDetailService
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(myClientDetailsService);
// 默认配置
// clients.inMemory().withClient("client_1")
// .resourceIds(DEMO_RESOURCE_ID)
// .authorizedGrantTypes("client_credentials", "refresh_token")
// .scopes("select")
// .authorities("client")
// .secret("123456");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.exceptionTranslator(exceptionTranslator()).authenticationManager(authenticationManager);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
//允许表单认证
oauthServer.allowFormAuthenticationForClients().accessDeniedHandler(accessDeniedHandler()).passwordEncoder(myPasswordEncoder);
}
@Bean
WebResponseExceptionTranslator exceptionTranslator() {
return myExceptionTranslator;
}
@Bean
AccessDeniedHandler accessDeniedHandler(){
OAuth2AccessDeniedHandler accessDeniedHandler = new OAuth2AccessDeniedHandler();
accessDeniedHandler.setExceptionTranslator(myExceptionTranslator);
accessDeniedHandler.setExceptionRenderer(new DefaultOAuth2ExceptionRenderer(){
@Override
public void handleHttpEntityResponse(HttpEntity<?> responseEntity, ServletWebRequest webRequest) throws Exception {
super.handleHttpEntityResponse(responseEntity, webRequest);
System.out.println("处理完成。。。");
}
});
return accessDeniedHandler;
}
}
- 配置安全
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
AuthenticationManager manager = super.authenticationManagerBean();
return manager;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.requestMatchers().anyRequest()
.and()
.authorizeRequests()
.antMatchers("/oauth/*").permitAll();
// @formatter:on
}
}
- 自定义验证(通过给对方提供的appKey和appSecret,验证是否允许访问系统接口)
@Component
public class MyClientDetailsService implements ClientDetailsService {
private static final String GOODS_RESOURCE_ID = "goods";
@Autowired
private AppSystemRepository appSystemRepository;
@Override
public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
AppSystem appSystem = appSystemRepository.getAppSystemByAppKey(clientId).get();
BaseClientDetails details = new BaseClientDetails(appSystem.getAppKey(),GOODS_RESOURCE_ID,"select","client_credentials,refresh_token","client");
details.setClientSecret(appSystem.getAppSecret());
return details;
}
}
- 自定义异常处理
@Component
public class MyExceptionTranslator extends DefaultWebResponseExceptionTranslator {
@Override
public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {
ResponseEntity<OAuth2Exception> responseEntity = super.translate(e);
System.out.println(responseEntity.getBody());
return responseEntity;
}
}
其他业务代码省略。完整代码已上传github:https://github.com/huzhiting/spring-boot-jpa-oauth2.0
【接口测试】
- /oauth/token 获取token
在访问系统接口前,必须通过/oauth/token 获取token,授权模式为客户端,而获取token,需要带上允许访问系统的appKey和appSecret,appKey与appSecret对应不一致,会提示认证失败。
获取token失败:
获取token成功:
- /orders 条件查询订单列表
在成功获取token后,即授权成功,那么再访问其他接口的时候,加上此token参数,才允许访问,否则会提示未授权。
查询订单列表失败:
查询订单列表成功:
【实战总结】
在之前,也用Spring boot 集成JWT做用户认证,感觉它们之间并没有什么可比性,但整个过程又有相似的体会,特意查了一下,对它们的解释:
JWT是一个“认证规范”,而OAuth是一个“开放标准网络协议”,并不是同一个类型的东西。
举个例子,你在服务器上存放了一些“资源”,JWT提供了一种用于发布接入令牌(Access Token),并对发布的签名接入令牌进行验证的方法,令牌(Token)本身包含了一系列声明,服务器可以根据这些声明限制用户对资源的访问。而你可以用这个token去访问你要访问的资源,而不需要每次都进行帐号密码认证。
而OAuth提供了一套详细的授权机制,你可以通过公开的或私有的设置,授权第三方应用访问特定资源。
上面的“你”指代用户。简单来说,JWT是用来证明你有权限访问特定资源,并提供一个安全方便的方式访问资源。而OAuth是你要授权第三方访问你的资源。