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.
insert image description here

(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)
insert image description here

(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)
insert image description here
(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.
insert image description here
(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

Guess you like

Origin blog.csdn.net/shy871/article/details/117597520