Spring boot integrates oauth2 authorization code mode operation record

Time:2020-11-20

Oauth2 authorization code mode

Licensing server

  1. Basic configuration of spring security, creating users and roles

    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    PasswordEncoder passwordEncoder(){
    return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
    .withUser(“johnny”).password(new BCryptPasswordEncoder().encode(“123”)).roles(“user”)
    .and()
    .withUser(“john”).password(new BCryptPasswordEncoder().encode(“123”)).roles(“admin”);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable().formLogin();
    }
    }

  2. To configure the token store, save the token in memory or redis

    @Configuration
    public class AccessTokenConfig {
    @Autowired
    RedisConnectionFactory redisConnectionFactory;
    /**
    *Store token in memory
    */
    @Bean
    TokenStore tokenStore() {
    return new RedisTokenStore(redisConnectionFactory);
    }
    }

  3. Authorization server should do two aspects of verification, one is to verify the client, the other is to verify the user. Configure the client’s ID, secret, resource ID, authorization type, authorization scope and redirection URI. Configure whether the token supports refreshing, the storage location of the token, the validity period of the token, and the validity period of the refresh token

    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    TokenStore tokenStore;
    @Autowired
    ClientDetailsService clientDetailsService;
    /**
    *The authorization server needs to check two aspects: one is to verify the client and the other is to verify the user,
    *Verify the user. Spring security has been configured. Here is the configuration verification client.
    *The client information can be stored in the database, which is actually relatively easy, similar to the user information stored in the database,
    *But in order to simplify the code, I still store the client information in memory,
    *Here we configure the client’s ID, secret, resource ID, authorization type, authorization scope and redirection URI.
    *There are four types of authorization, of which refresh is not included_ The token type,
    *But in practice, refresh_ Token is also counted as one.
    */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.inMemory()
    .withClient(“johnny”)
    .secret(new BCryptPasswordEncoder().encode(“123”))
    .resourceIds(“res1”)
    .authorizedGrantTypes(“authorization_code”, “refresh_token”)
    .scopes(“all”)
    .redirectUris(“http://localhost:8082/index.html”);
    }
    /**
    *Authorization server security configurer is used to configure the security constraints of the token endpoint,
    *That is, who can access the endpoint and who can’t access it. Checktokenaccess refers to an endpoint of token verification,
    *This endpoint is set to be accessible directly
    *After receiving the token, the server needs to verify the validity of the token.
    */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
    security.checkTokenAccess(“permitAll()”).allowFormAuthenticationForClients();
    }
    /**
    *Authorization server endpoints configurer is used to configure the access endpoint and token service of the token.
    *Authorization code services are used to configure the storage of authorization codes. Here we are in memory,
    *Token services is used to configure the storage of tokens, namely access_ The storage location of the token. Here we also store it in the memory first.
    *Authorization codes are used to obtain tokens, which are invalid once used. Tokens are used to obtain resources
    */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    endpoints.authorizationCodeServices(authorizationCodeServices())
    .tokenServices(authorizationServerTokenServices());
    }
    /**
    *Authorization code services are used to configure the storage of authorization codes. Here we are in memory
    */
    @Bean
    AuthorizationCodeServices authorizationCodeServices() {
    return new InMemoryAuthorizationCodeServices();
    }
    /**
    *The token services bean is mainly used to configure some basic information of token,
    *For example, whether the token supports refreshing, the storage location of the token, the validity period of the token, and the validity period of the refresh token.
    *The validity period of a token is easy to understand. Refresh the validity period of a token. When the token is about to expire, we need to obtain a new token,
    *When you get a new token, you need to have a certificate information. This voucher information is not the old token, but another refresh_ Token, this refresh_ Token also has a valid period.
    */
    @Bean
    AuthorizationServerTokenServices authorizationServerTokenServices() {
    DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
    defaultTokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 3);
    defaultTokenServices.setAccessTokenValiditySeconds(60 * 60 * 2);
    defaultTokenServices.setSupportRefreshToken(true);
    defaultTokenServices.setTokenStore(tokenStore);
    defaultTokenServices.setClientDetailsService(clientDetailsService);
    return defaultTokenServices;
    }
    }

Resource server

Configure access_ Check address and client of token_ id、client_ Secret these three information, when users come to the resource server to request resources, they will carry the previous access_ Token. Through the configuration here, you can verify whether the token is correct

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
/**
*Kenservices we have configured an instance of remotetokenservices,
*This is because the resource server and the authorization server are separated. The resource server and the authorization server are put together, so there is no need to configure remotetoken services
*Access is configured in remotetokenservices_ Check address and client of token_ id、client_ Secret these three messages,
*When users come to the resource server to request resources, they will carry the previous access_ token,
*Through the configuration here, you can check whether the token is correct or not
*/
@Bean
RemoteTokenServices remoteTokenServices() {
RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
remoteTokenServices.setCheckTokenEndpointUrl(“http://localhost:8080/oauth/check_token”);
remoteTokenServices.setClientId(“johnny”);
remoteTokenServices.setClientSecret(“123”);
return remoteTokenServices;
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(“res1”).tokenServices(remoteTokenServices());
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(“/admin/**”).hasRole(“admin”)
.anyRequest().authenticated();
}

}

//Resource response API
@RestController
public class HelloController {
@GetMapping(“/hello”)
public String hello(){
return “hello”;
}
@GetMapping(“/admin/hello”)
public String helloAdmin(){
return “hello,Admin”;
}
}

Third party applications

Click the hyperlink to realize the third party login. The parameters of the hyperlink are as follows:

  • client_ ID client ID, which is filled in according to our actual configuration in the authorization server
  • response_ Type is the response type, here is code, which means response is an authorization code
  • redirect_ Uri refers to the redirection address after successful authorization. Here, it indicates returning to the home page of the third-party application
  • Scope indicates the scope of authorization

<!DOCTYPE html>
<html lang=”en” xmlns:th=”http://www.thymeleaf.org”>
<head>
<meta charset=”UTF-8″>
<title>Title</title>
</head>
<body>
Hello, Gao Yongqiang
<! — get the authorization code from the authorization server — >
<a href=” http://localhost :8080/oauth/authorize?client_ id=johnny&response_ type=code&scope=all&redirect_ uri= http://localhost :8082/ index.html “> third party authorized login</a>
<h1 th:text=”${msg}”></h1>
</body>
</html>

@Controller
public class HelloController {
@Autowired
TokenTask tokenTask;
@Autowired
RestTemplate restTemplate;
@GetMapping(“/index.html”)
/**
*Normally, access_ Token we may need a regular task to maintain,
*You don’t need to get the latest access every time you request the page_ Token
*/
public String hello(String code,Model model){
model.addAttribute(“msg”,tokenTask.getData(code));
return “index”;
}
}

@Component
@EnableScheduling
public class TokenTask {
@Autowired
RestTemplate restTemplate;
public String access_token = “”;
public String refresh_token = “”;

/**
*Get token and refreshtoken according to authorization code
*/
public String getData(String code) {
if (“”.equals(access_token) && code != null) {
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add(“code”, code);
map.add(“client_id”, “johnny”);
map.add(“client_secret”, “123”);
map.add(“redirect_uri”, “http://localhost:8082/index.html”);
map.add(“grant_type”, “authorization_code”);
Map<String, String> resp = restTemplate.postForObject(“http://localhost:8080/oauth/token”, map, Map.class);
access_token = resp.get(“access_token”);
refresh_token = resp.get(“refresh_token”);
return getResourceFromServer();
} else {
return getResourceFromServer();
}
}
/**
*Request server resources with token
*/
public String getResourceFromServer() {
try {
HttpHeaders headers = new HttpHeaders();
headers.add(“Authorization”, “Bearer ” + access_token);
HttpEntity<Object> httpEntity = new HttpEntity<>(headers);
ResponseEntity<String> entity = restTemplate.exchange(“http://localhost:8081/admin/hello”, HttpMethod.GET, httpEntity, String.class);
return entity.getBody();
} catch (RestClientException e) {
Return “resource request failed”;
}
}
/**
*Refresh token every 30 minutes
*/
@Scheduled(cron = “0 0/30 * * * ?”)
public void tokenTask() {
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add(“client_id”, “johnny”);
map.add(“client_secret”, “123”);
map.add(“refresh_token”, refresh_token);
map.add(“grant_type”, “refresh_token”);
Map<String, String> resp = restTemplate.postForObject(“http://localhost:8080/oauth/token”, map, Map.class);
access_token = resp.get(“access_token”);
refresh_token = resp.get(“refresh_token”);
System.out.println (“> > > > > > > refresh token time:” new simpledateformat (“yyyy / mm / DD HH: mm: SS”). Format (New date()));
System.out.println(“access_token = ” + access_token);
System.out.println(“refresh_token = ” + refresh_token);
}
}

Token stored in redis

  1. rely on

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
    </dependency>

  2. Redis configuration

    spring.redis.host=127.0.0.1
    spring.redis.port=6379
    spring.redis.password=123

  3. Modify tokenstore instance

    @Configuration
    public class AccessTokenConfig {
    @Autowired
    RedisConnectionFactory redisConnectionFactory;
    //Return to redistokenstore
    @Bean
    TokenStore tokenStore() {
    return new RedisTokenStore(redisConnectionFactory);
    }
    }

Token management – regular refresh

<! — get the authorization code from the authorization server — >
<a href=” http://localhost :8080/oauth/authorize?client_ id=johnny&response_ type=code&scope=all&redirect_ uri= http://localhost :8082/ index.html “> third party authorized login</a>

/**
* @PackageName: com.johnny.clientapp
* @ClassName:
*@ Description: token auto refresh
* @author: Johnny
* @date: 2020/4/26
*/
@Component
@EnableScheduling
public class TokenTask {
@Autowired
RestTemplate restTemplate;
public String access_token = “”;
public String refresh_token = “”;

/**
*Get token and refreshtoken according to authorization code
*/
public String getData(String code) {
if (“”.equals(access_token) && code != null) {
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add(“code”, code);
map.add(“client_id”, “johnny”);
map.add(“client_secret”, “123”);
map.add(“redirect_uri”, “http://localhost:8082/index.html”);
map.add(“grant_type”, “authorization_code”);
Map<String, String> resp = restTemplate.postForObject(“http://localhost:8080/oauth/token”, map, Map.class);
access_token = resp.get(“access_token”);
refresh_token = resp.get(“refresh_token”);
return getResourceFromServer();
} else {
return getResourceFromServer();
}
}

/**
*Request server resources with token
*/
public String getResourceFromServer() {
try {
HttpHeaders headers = new HttpHeaders();
headers.add(“Authorization”, “Bearer ” + access_token);
HttpEntity<Object> httpEntity = new HttpEntity<>(headers);
ResponseEntity<String> entity = restTemplate.exchange(“http://localhost:8081/admin/hello”, HttpMethod.GET, httpEntity, String.class);
return entity.getBody();
} catch (RestClientException e) {
Return “resource request failed”;
}
}

/**
*Refresh token every 30 minutes
*/
@Scheduled(cron = “0 0/30 * * * ?”)
public void tokenTask() {
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add(“client_id”, “johnny”);
map.add(“client_secret”, “123”);
map.add(“refresh_token”, refresh_token);
map.add(“grant_type”, “refresh_token”);
Map<String, String> resp = restTemplate.postForObject(“http://localhost:8080/oauth/token”, map, Map.class);
access_token = resp.get(“access_token”);
refresh_token = resp.get(“refresh_token”);
System.out.println (“> > > > > > > refresh token time:” new simpledateformat (“yyyy / mm / DD HH: mm: SS”). Format (New date()));
System.out.println(“access_token = ” + access_token);
System.out.println(“refresh_token = ” + refresh_token);
}
}

@Controller
public class HelloController {
@Autowired
TokenTask tokenTask;
@Autowired
RestTemplate restTemplate;
@GetMapping(“/index.html”)
/**
*Normally, access_ Token we may need a regular task to maintain,
*You don’t need to get the latest access every time you request the page_ Token
*/
public String hello(String code,Model model){
model.addAttribute(“msg”,tokenTask.getData(code));
return “index”;
}
}