问题
最近有个需求是要给系统里面的所有REST请求,弄一个token,然后,那着这个访问token,去掉接口。阮一峰写了两遍文章,值得我们一看:
这里假设我们已经实现spring的rest api了。
Maven
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
...
<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.0.4.RELEASE</version>
</dependency>
...
application.properties
security.oauth2.client.client-id=client
security.oauth2.client.client-secret=secret
security.oauth2.client.authorized-grant-types=password,authorization_code,refresh_token,implicit
security.oauth2.client.scope=read,write,trust
security.oauth2.client.access-token-validity-seconds=120
security.oauth2.client.refresh-token-validity-seconds=600
#超级管理员
admin.username=admin
amdin.password=pwd
Java
Application.Java
package com.zyl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
@SpringBootApplication
// 启用认证服务器
@EnableAuthorizationServer
// 启用资源服务器
@EnableResourceServer
public class Application {
public static void main(String[] args) {
SpringApplication.run(BudgetApplication.class, args);
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
// 添加密码编码器
return new BCryptPasswordEncoder();
}
@Bean
public TokenStore tokenStore() {
// 添加内存保存token
return new InMemoryTokenStore();
}
}
这里是单模块的工程,没有把认证服务和资源服务进行分离处理。这里主要就是启用认证服务器和资源服务器,还添加密码编码器和token内存保存器,密码编码和token内存保存,是后面待使用。
Oauth2SecurityConfig.Java
package com.zyl.config;
import com.zyl.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@EnableWebSecurity
public class Oauth2SecurityConfig extends WebSecurityConfigurerAdapter {
private final BCryptPasswordEncoder bCryptPasswordEncoder;
private final UserService userService;
@Autowired
public Oauth2SecurityConfig(
BCryptPasswordEncoder bCryptPasswordEncoder, UserService userService) {
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
this.userService = userService;
}
// 配置这个bean会在做AuthorizationServerConfigurer配置的时候使用
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(bCryptPasswordEncoder);
}
// @Override
// public void configure(WebSecurity web) throws Exception {
// // TODO 关闭spring security
// web.ignoring().antMatchers("/**");
// }
}
这里主要就是配置@EnableWebSecurity,启用Spring Security,还有就是实例化了AuthenticationManager认证管理器,以及注入了userDetailsService用户服务和BCryptPasswordEncoder密码编码器,给认证的过程中配置了一个用户服务和密码编码器。这里的UserService是对UserDetailsService接口,后面我们再讨论这个问题。
AuthorizationServerConfiguration.Java
package com.zyl.config;
import com.zyl.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Component;
@Component
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
private final TokenStore tokenStore;
private final AuthenticationManager authenticationManager;
private final UserService userService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
// 从资源文件中读取http基本认证username
@Value("${security.oauth2.client.client-id:client}")
private String client;
// 从资源文件中读取oauth2的授权类型
@Value("${security.oauth2.client.authorized-grant-types:password,authorization_code,refresh_token,implicit}")
private String[] grantTypes;
// 从资源文件中读取http基本认证password
@Value("${security.oauth2.client.client-secret:pwd}")
private String secret;
// 从资源文件中读取oauth2的权限范围
@Value("${security.oauth2.client.scope:read,write,trust}")
private String scopes;
/**
* security.oauth2.client.access-token-validity-seconds=120
* Access token is only valid for 2 minutes.
*/
@Value("${security.oauth2.client.access-token-validity-seconds:120}")
private int accessTokenValiditySeconds;
/**
* security.oauth2.client.refresh-token-validity-seconds=600
* Refresh token is only valid for 10 minutes.;
*/
@Value("${security.oauth2.client.refresh-token-validity-seconds:600}")
private int refreshTokenValiditySeconds;
@Autowired
public AuthorizationServerConfiguration(TokenStore tokenStore, AuthenticationManager authenticationManager, UserService userService, BCryptPasswordEncoder bCryptPasswordEncoder) {
this.tokenStore = tokenStore;
this.authenticationManager = authenticationManager;
this.userService = userService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore).authenticationManager(authenticationManager).userDetailsService(userService);
}
@Override
public void configure(
ClientDetailsServiceConfigurer clients
) throws Exception {
clients.inMemory()
.withClient(client)
.authorizedGrantTypes(grantTypes)
.secret(bCryptPasswordEncoder.encode(secret))
.scopes(scopes)
.accessTokenValiditySeconds(accessTokenValiditySeconds)
.refreshTokenValiditySeconds(refreshTokenValiditySeconds);
}
}
这里主要是对OAuth2.0认证的设置,以及认证的token保存和认证管理器的配置,已经自定义的用户认证服务userService。
UserService.Java
package com.zyl.service;
import com.zyl.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Optional;
@Service
public class UserService implements UserDetailsService {
// 注入密码编码器
private final BCryptPasswordEncoder bCryptPasswordEncoder;
// 从资源文件中读取超级管理员用户名
@Value("${admin.username:zyl}")
private String adminUsername;
// 从资源文件中读取超级管理员密码
@Value("${amdin.password:pwd}")
private String adminPassword;
@Autowired
public UserService(
BCryptPasswordEncoder bCryptPasswordEncoder
) {
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
@Override
public UserDetails loadUserByUsername(String username) {
// 查找用户
User user = findByUid(username);
...
org.springframework.security.core.userdetails.User.UserBuilder builder = org.springframework.security.core.userdetails.User.withUsername(username);
if (user != null){
builder.password(user.getPassword());
builder.roles("USER");
return builder.build();
} else {
// 超级管理员
if (username.equals(adminUsername)){
builder.password(bCryptPasswordEncoder.encode(adminPassword));
builder.roles("ADMIN");
return builder.build();
}
// TODO 没找用户处理
...
}
}
}
这里主要从系统中查找到对应的用户,并将找到用户交给Spring进行认证判断。
User.Java
package com.zyl.model;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
/**
* 用户uid
*/
private String uid;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
...
}
这个是一个简单的java bean。
PostMan
接下来,通过postman做三件事情:
- 通过认证,获得用户访问token和刷新token
- 通过访问token调用api
- 访问token到期失效后,刷新token
授权认证

根据资源文件中配置:
security.oauth2.client.client-id=client
security.oauth2.client.client-secret=secret
设置http基本认证的用户名和密码。 
这样就完成OAuth的访问token的获取。
{
"access_token": "a5a67cf1-70df-450c-8241-8409abe988e2",
"token_type": "bearer",
"refresh_token": "56675215-e22a-4bff-8489-fbc3a28699de",
"expires_in": 119,
"scope": "read,write,trust"
}
调用API
获得到访问Token后,需要调用API,只需要在请求头中添加Authorization即可:
Authorization: Bearer ...
刷新Token
如果访问Token过期了,还需要刷新该授权:
注意,这仍然需要配置http基本认证的用户和密码,才能够进行post刷新token的。
{
"access_token": "edb39ddc-1257-4e53-96c0-66232a8d158e",
"token_type": "bearer",
"refresh_token": "56675215-e22a-4bff-8489-fbc3a28699de",
"expires_in": 119,
"scope": "read,write,trust"
}
总结
好,这样就给我们的REST接口加上了OAuth2.0的认证服务。
参考:
Secure Spring REST API using OAuth2
Spring REST API + OAuth2 + AngularJS
来源:oschina
链接:https://my.oschina.net/u/168875/blog/2045676
