After writing this case, I'm afraid I can't understand the OAuth2 login process with the interviewer?

Yesterday, I introduced the basic concepts of OAuth2 with my friends. Before explaining Spring Cloud Security OAuth2, I will first go through the actual code to go through the various authorization modes in OAuth2 with my friends. Today we look at the most commonly used and most Complex authorization code model.

In this article, I will go through a complete Demo . Note that it is a complete Demo , which leads my friends to walk through the authorization code mode.

If you have n’t read the previous article, you can read it first. This will help you understand some of the concepts in this article:

1. Case Architecture

Because OAuth2 involves a lot of things, most of the online cases are simplified. For many beginners, the simplified cases look at people in fog, so Songge wants to build a complete test case himself this time. In this case, it mainly includes the following services:

  • third-party usage
  • Authorization server
  • Resource server
  • user

I use a table to organize for everyone:

project port Remarks
auth-server 8080 Authorization server
user-server 8081 Resource server
client-app 8082 third-party usage

That is to say, I will provide and test all the roles involved in our common OAuth2 authorization code mode login, so as to maximize the understanding of the working principle of OAuth2 (the case source code can be downloaded at the end of the article).

Note: Friends must first look at the OAuth2 authorization code mode login process described by Songge in the previous article , and then come to study this article.

Let's first create an empty Maven parent project. After it is created, there is nothing to add and no code to write. We will build this submodule in this parent project.

2. Authorization server setup

First, we build an authorization service called auth-server. When building, we choose the following three dependencies:

  • web
  • spring cloud security
  • spirng cloud OAuth2

After the project is created, first provide a basic configuration of Spring Security:

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

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("sang")
                .password(new BCryptPasswordEncoder().encode("123"))
                .roles("admin")
                .and()
                .withUser("javaboy")
                .password(new BCryptPasswordEncoder().encode("123"))
                .roles("user");
    }

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

In this code, for the sake of brevity, I will not store the Spring Security user in the database, but directly in the memory.

Here I created a user named sang with password 123 and role admin. I also configured a form login.

The purpose of this configuration is to configure users. For example, if you want to use WeChat to log in to a third-party website, in this process, you have to log in to WeChat first, which requires your username / password information. Then what we configure here is actually the user ’s username / password / role information.

After the basic user information configuration is complete, let's configure the authorization server:

@Configuration
public class AccessTokenConfig {
    @Bean
    TokenStore tokenStore() {
        return new InMemoryTokenStore();
    }
}
@EnableAuthorizationServer
@Configuration
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
    @Autowired
    TokenStore tokenStore;
    @Autowired
    ClientDetailsService clientDetailsService;

    @Bean
    AuthorizationServerTokenServices tokenServices() {
        DefaultTokenServices services = new DefaultTokenServices();
        services.setClientDetailsService(clientDetailsService);
        services.setSupportRefreshToken(true);
        services.setTokenStore(tokenStore);
        services.setAccessTokenValiditySeconds(60 * 60 * 2);
        services.setRefreshTokenValiditySeconds(60 * 60 * 24 * 3);
        return services;
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.checkTokenAccess("permitAll()")
                .allowFormAuthenticationForClients();
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("javaboy")
                .secret(new BCryptPasswordEncoder().encode("123"))
                .resourceIds("res1")
                .authorizedGrantTypes("authorization_code","refresh_token")
                .scopes("all")
                .redirectUris("http://localhost:8082/index.html");
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authorizationCodeServices(authorizationCodeServices())
                .tokenServices(tokenServices());
    }
    @Bean
    AuthorizationCodeServices authorizationCodeServices() {
        return new InMemoryAuthorizationCodeServices();
    }
}

This code is a bit long, let me explain to you one by one:

  1. First of all, we provide an instance of TokenStore. This refers to where you want to store the generated Token. We can store it in Redis, it can also be stored in memory, or it can be combined with JWT, etc. Here, we will store it in memory first , So provide an instance of InMemoryTokenStore.
  2. Next, we create the AuthorizationServer class inherited from AuthorizationServerConfigurerAdapter to further configure the authorization server. The AuthorizationServer class remember to add @EnableAuthorizationServer annotation, which means that the authorization server automatic configuration is turned on.
  3. In the AuthorizationServer class, we actually rewrite three configure methods.
  4. AuthorizationServerSecurityConfigurer is used to configure the security constraints of the token endpoint, that is, who can access this endpoint and who cannot. checkTokenAccess refers to an endpoint for token verification. We set this endpoint to be directly accessible (later, when the resource server receives the token, it needs to verify the validity of the token and it will access this endpoint).
  5. ClientDetailsServiceConfigurer is used to configure the detailed information of the client. In the last article , Songge told everyone that the authorization server needs to be tested in two aspects, one is to verify the client, the other is to verify the user, verify User, we have already configured it before, here is the configuration verification client. We can store the client information in the database, which is actually relatively easy, similar to storing user information in the database, but here to simplify the code, I still store the client information in memory, here we have configured the client ID, secret, resource id, authorization type, authorization scope, and redirect uri. In the last article , I talked about four types of authorization . The four types do not include the refresh_token type, but in actual operation, refresh_token is also counted as one.
  6. AuthorizationServerEndpointsConfigurer is used here to configure the token access endpoint and token service. authorizationCodeServices is used to configure the storage of authorization codes, here we are stored in memory, and tokenServices is used to configure the storage of tokens, that is, the storage location of access_token, here we are also stored in memory first. Some friends will ask, what is the difference between the authorization code and the token? The authorization code is used to obtain the token, and it will be invalid once used. The token is used to obtain resources. If you are confused, it is recommended to re-read the previous article . Come and talk to everyone
  7. The tokenServices bean is mainly used to configure some basic information of the Token, such as whether the Token supports refresh, the storage location of the Token, the validity period of the Token, and the validity period of refreshing the Token, etc. The validity period of Token is easy to understand. Let me talk about refreshing the validity period of Token. When the Token is about to expire, we need to obtain a new Token. When obtaining a new Token, we need to have a credential information. This credential information is not the old Token. But another refresh_token, this refresh_token also has a validity period.

Well, after this, even if our authorization server is configured, we will start the authorization server.

3. Resource server setup

Next we build a resource server. In the examples you see online, most of the resource servers are placed together with the authorization server. If the project is relatively small, it is okay to do so, but if it is a large project, this approach is not suitable.

The resource server is used to store the user's resources, such as your images and openid on WeChat. After the user gets the access_token from the authorization server, the resource server can then request data through the access_token.

We create a new Spring Boot project, called user-server, as our resource server, when creating, add the following dependencies:

After the project is successfully created, add the following configuration:

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Bean
    RemoteTokenServices tokenServices() {
        RemoteTokenServices services = new RemoteTokenServices();
        services.setCheckTokenEndpointUrl("http://localhost:8080/oauth/check_token");
        services.setClientId("javaboy");
        services.setClientSecret("123");
        return services;
    }
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId("res1").tokenServices(tokenServices());
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/**").hasRole("admin")
                .anyRequest().authenticated();
    }
}

This configuration code is very simple, let me briefly say:

  1. tokenServices We have configured an instance of RemoteTokenServices. This is because the resource server and the authorization server are separate. The resource server and the authorization server are put together. There is no need to configure RemoteTokenServices.
  2. In RemoteTokenServices, we configure the access_token verification address, client_id, and client_secret. When users come to the resource server to request resources, they will carry an access_token. Through the configuration here, we can verify whether the token is correct.
  3. Finally, configure the resource interception rules. This is the basic wording in Spring Security, and I will not repeat them.

Next, let's configure two test interfaces:

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }
    @GetMapping("/admin/hello")
    public String admin() {
        return "admin";
    }
}

After this, even if our resource server is successfully configured.

4. Third-party application building

Next, build our third-party application.

Note that the third-party application is not necessary, the code written below can also be tested with POSTMAN, this little partner can try it by themselves.

The third-party application is an ordinary Spring Boot project, Thymeleaf dependency and Web dependency are added when creating:

In the resources / templates directory, create index.html with the following content:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>江南一点雨</title>
</head>
<body>
你好,江南一点雨!

<a href="http://localhost:8080/oauth/authorize?client_id=javaboy&response_type=code&scope=all&redirect_uri=http://localhost:8082/index.html">第三方登录</a>

<h1 th:text="${msg}"></h1>
</body>
</html>

This is a Thymeleaf template. Click on the hyperlink to achieve third-party login. The parameters of the hyperlink are as follows:

  • client_id Client ID, fill in according to our actual configuration in the authorization server.
  • response_type indicates the response type. Here, code indicates that the response is an authorization code.
  • redirect_uri represents the redirection address after successful authorization, and here represents returning to the homepage of the third-party application.
  • scope indicates the authorization scope.

The data in the h1 tag comes from the resource server. When the authorization server passes, we take the access_token to the resource server to load the data. The loaded data is displayed in the h1 tag.

Next, let's define a HelloController:

@Controller
public class HelloController {
    @Autowired
    RestTemplate restTemplate;

    @GetMapping("/index.html")
    public String hello(String code, Model model) {
        if (code != null) {
            MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
            map.add("code", code);
            map.add("client_id", "javaboy");
            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);
            String access_token = resp.get("access_token");
            System.out.println(access_token);
            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);
            model.addAttribute("msg", entity.getBody());
        }
        return "index";
    }
}

In this HelloController, we define the address of /index.html.

If the code is not null, that is, if it is redirected to this address through the authorization server, then we do the following two operations:

  1. According to get the code, to request http://localhost:8080/oauth/tokenaddress to obtain the Token, the returned data is structured as follows:
{
    "access_token": "e7f223c4-7543-43c0-b5a6-5011743b5af4",
    "token_type": "bearer",
    "refresh_token": "aafc167b-a112-456e-bbd8-58cb56d915dd",
    "expires_in": 7199,
    "scope": "all"
}

access_token is the token we need to request data, refresh_token is the token we need to refresh the token, expires_in indicates how long the token is valid.

  1. Next, request the resource server based on the access_token we got. Note that the access_token is passed through the request header, and finally put the data returned by the resource server into the model.

Here I just give a simple example, the purpose is to pass this process with everyone. Normally, we may need a scheduled task to maintain the access_token, instead of getting it every time the page is requested, and periodically get the latest access_token. . In later articles, Song Ge will continue to improve this case, and then come back to solve these details with you.

OK, after the code is written, we can start the third-party application and start testing.

5. Test

Next we go to test.

First we went to visit http://localhost:8082/index.htmlthe page with the following results:

Then we click the third-party login hyperlink, and after clicking, we will enter the default login page of the authorization server:

Next, we enter the user information configured in the authorization server to log in. After successful login, you will see the following page:

On this page, we can see a prompt asking whether to authorize the javaboy user to access the protected resource, we choose approve, and then click the Authorize button below. After clicking, the page will automatically jump back to me Of third-party applications:

Please note that at this time, there is a code parameter in the address bar, which is the authorization code given by the authorization server. With this authorization code, we can request the access_token, and the authorization code will be invalid once used.

At the same time, everyone notices that there is an additional admin on the page. This admin is the data requested from the resource server.

Of course, we have configured two users in the authorization server, you can also try to log in with the user javaboy / 123, because this user does not have the admin role, so this user will not be able to get the admin string, the error message is as follows :

This little buddy can test it by myself, I will not show it again.

Finally, I am saying that this is not the ultimate version, but a prototype. In the following article, Song Ge will take you to continue to improve this case.

Well, follow the WeChat public account Jiangnan a little rain , reply oauth2 Download the complete case of this article.

发布了574 篇原创文章 · 获赞 6896 · 访问量 476万+

Guess you like

Origin blog.csdn.net/u012702547/article/details/105504300