oauth2.0 authorization mode and springboot integration instance
In the recent projects I participated in, springboot was used to integrate oauth2.0 to realize the account password verification login authorization function, and to realize database account password verification, domain control user password verification and Redis cache token information and other functions in the function. Combined with the actual use of the project and related predecessors' literature, this article initially summarizes and records the relevant knowledge of Oauth2.0 and gives a preliminary example of use, so as to facilitate subsequent in-depth study and provide relevant reference for latecomers; there are inevitably omissions in the article, and readers are welcome to advise , thank you very much!
1. Introduction to oauth2.0
OAuth (Open Authorization) is an open standard that allows users to authorize third-party mobile applications to access their information stored on another service provider, without the need to provide usernames and passwords to third-party mobile applications or share all content of their data , OAuth2.0 is a continuation version of the OAuth protocol, but it is not backward compatible with OAuth 1.0, that is, OAuth1.0 is completely abolished.
Authorization Server Authorization Server
Resource Server Resource Server
authorization code mode (authorization code)
implicit mode (implicit)
password mode (resource owner password credentials)
client mode (client credentials)
2. Four modes of oauth2.0
2.1 Authorization code authorization mode
The authorization code method means that the third-party application first applies for an authorization code, and then uses the code to obtain a token.
The most commonly used process has the highest security and is suitable for web applications with backends. The authorization code is sent through the front end, the token is stored in the back end, and all communication with the resource server is done in the back end. Such separation of front and back ends can avoid token leakage.
(1) Trigger access authentication service
https://b.com/oauth/authorize?
response_type=code&
client_id=CLIENT_ID&
redirect_uri=CALLBACK_URL&
scope=read
The response_type parameter indicates the request to return the authorization code (code), the client_id parameter lets B know who is requesting, the redirect_uri parameter is the jump URL after B accepts or rejects the request, and the scope parameter indicates the required authorization scope (here is read-only)
(2) Authorize the authentication service interface, trigger the callback to pass the authorization code code value
https://a.com/callback?code=AUTHORIZATION_CODE
(3) Initiate a request to obtain token
https://b.com/oauth/token?
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET&
grant_type=authorization_code&
code=AUTHORIZATION_CODE&
redirect_uri=CALLBACK_URL
The client_id parameter and client_secret parameter are used to let B confirm the identity of A (the client_secret parameter is confidential, so the request can only be sent at the backend), the value of the grant_type parameter is AUTHORIZATION_CODE, indicating that the authorization method used is the authorization code, and the code parameter is the above The authorization code obtained in one step, the redirect_uri parameter is the callback URL after the token is issued.
(4) Return the token token
{
access_token: "e2721525-c1fd-41fc-96c4-ecc419b41ab6",
expires_in: 7199,
refresh_token: "c52dc91b-5068-44bf-9c0a-cfd7da9a2745",
scope: "scope",
token_type: "bearer"
}
2.2 Implicit authorization mode
A pure front-end application, without a backend, must store tokens in the frontend, allowing tokens to be issued directly to the frontend. This method has no intermediate step of authorization code, so it is called "implicit authorization" (implicit)
(1) Trigger the access authorization interface
https://b.com/oauth/authorize?
response_type=token&
client_id=CLIENT_ID&
redirect_uri=CALLBACK_URL&
scope=read
The response_type parameter is token, indicating that the token is required to be returned directly.
(2) Call back redirect_uri to return token
https://a.com/callback#token=ACCESS_TOKEN
2.3 Password mode
If a certain application is highly trusted, the user is also allowed to directly tell the application the user name and password. The application uses your password to apply for a token. This method is called "password" (password)
(1) User name + password access authentication service to obtain token
https://oauth.b.com/token?
grant_type=password&
username=USERNAME&
password=PASSWORD&
client_id=CLIENT_ID
The grant_type parameter is the authorization method, where password means "password type", username and password are the user name and password respectively, and the token information is returned directly through the request.
2.4 Client credential mode
The token given in this way is for third-party applications, not for users. The application wants to interact with the authorization server and resource server in its own name, that is, it is possible for multiple users to share the same token through the application. token.
(1) Access authentication service
https://oauth.b.com/token?
grant_type=client_credentials&
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET
The grant_type parameter is equal to client_credentials, indicating that the credential is used, and client_id and client_secret are used to allow the authentication service to confirm the identity of the application service.
3. Using tokens and updating tokens
3.1 Using tokens
request header added
Authorization: Bearer ACCESS_TOKEN
3.2 Renew token
When issuing a token, two tokens are issued at once, one is used to obtain data, and the other is used to obtain a new token (refresh token field). Before the token expires, the user uses the refresh token to send a request to renew the token.
https://b.com/oauth/token?
grant_type=refresh_token&
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET&
refresh_token=REFRESH_TOKEN
The grant_type parameter is refresh_token, which means that the token is required to be updated, the client_id parameter and client_secret parameter are used to confirm the identity, and the refresh_token parameter is the token used to update the token.
4. oauth2.0 use case
Springboot integrates oauth2.0 and uses Redis as an example of caching. The current participating projects implement account password verification login and restrict access to uri functions according to j roles, so the password mode is used in the example
(0) import jar
compile 'org.springframework.cloud:spring-cloud-starter-security'
compile 'org.springframework.cloud:spring-cloud-starter-oauth2'
(1) Authentication and authorization service
@Configuration
@EnableAuthorizationServer
public class LocalAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private static final String REDIS_TOKEN_PREFIX = "LOCAL:OAUTH2:";
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Autowired
private ClientDetailsService clientDetailsService;
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()")
.checkTokenAccess("permitAll()")
.passwordEncoder(NoOpPasswordEncoder.getInstance());
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenServices(tokenServices()).authenticationManager(authenticationManager);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//此处采用密码模式 password, 授权码模式 authorization_code,隐式授权模式 implicit,客户端凭证模式 client_credentials
clients.inMemory()
.withClient("client_id")
.secret("client_secret")
.authorizedGrantTypes("password","refresh_token")
.scopes("scope");
}
@Bean
public TokenStore tokenStore(){
RedisTokenStore redisTokenStore = new RedisTokenStore(redisTemplate.getConnectionFactory());
redisTokenStore.setPrefix(REDIS_TOKEN_PREFIX);
return redisTokenStore;
}
@Primary
@Bean
public AuthorizationServerTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
// token 过期时间 2小时
defaultTokenServices.setAccessTokenValiditySeconds(7200);
// refresh_token 过期时间 2天
defaultTokenServices.setRefreshTokenValiditySeconds(172800);
defaultTokenServices.setReuseRefreshToken(true);
return defaultTokenServices;
}
}
(2) web security configuration, account password
@Configuration
@EnableWebSecurity
public class LocalSecurityWebAdapter extends WebSecurityConfigurerAdapter {
@Autowired
private LocalUserService userService;
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
}
}
/**
* 数据库用户账户密码角色信息
*/
@Service("userDetailsService")
public class LocalUserService implements UserDetailsService {
/**
* 根据登陆用户名称获取用户角色信息
* @param username
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
// 数据库查询获取账户username对应的密码password和角色名称列表
List<String> roles = new ArrayList<String>();
String pwd = "";
for(String role: roles){
grantedAuthorities.add(new SimpleGrantedAuthority(role));
}
return new User(username,new BCryptPasswordEncoder().encode(pwd),grantedAuthorities);
}
}
(2) Configuration resource service
@Configuration
@EnableResourceServer
public class LocalResourceConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
// 此处可以通过配置指定,哪些角色可以访问哪些uri
// 放开部分api接口不需要进行权限校验处理
http.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS,"/**").permitAll();
http.authorizeRequests()
.antMatchers("/oauth/token","/oauth/check_token").permitAll();
// uri路径匹配规则:
//1.首先放置 permitAll规则
//2.其次放置 精确规则 完整路径规则
//3.最后放置 正则匹配规则,范围小的在前,范围大的在后
//4.规则依次匹配,匹配到后不再往下匹配,故用于最大权限的角色需要所有规则都赋予
// http.authorizeRequests().antMatchers(uri1).permitAll();
// http.authorizeRequests().antMatchers(uri2).hasAnyAuthority("role1","role2");
http.authorizeRequests().anyRequest().authenticated();
}
private static class Oauth2RequestMatcher implements RequestMatcher{
// 请求头 Authorization 有Bearer token或者请求参数中包含access_token才进行校验
@Override
public boolean matches(HttpServletRequest request) {
String auth = request.getHeader("Authorization");
boolean haveOauth2Token = (auth!=null) && auth.startsWith("Bearer");
boolean haveAccessToken = request.getParameter("access_token")!=null;
return haveOauth2Token || haveAccessToken;
}
}
@Bean
HttpSessionEventPublisher httpSessionEventPublisher(){
return new HttpSessionEventPublisher();
}
@Bean
public SessionRegistry sessionRegistry(){
SessionRegistry sessionRegistry = new SessionRegistryImpl();
return sessionRegistry;
}
}
5. References
[1] https://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html
[2] https://www.cnblogs.com/sky-chen/archive/2019/03/13/10523882.html#autoid-1-1-1-0-0-0