Xuecheng Online Notes + Stepping on the Pit (11) - Introduction to Authentication and Authorization, Gateway Authentication, SpringSecurity+JWT+OAuth2

navigation:

[Dark Horse Java Notes + Stepping Pit Summary] JavaSE+JavaWeb+SSM+SpringBoot+Riji Takeaway+SpringCloud+Dark Horse Tourism+Grain Mall+Xuecheng Online+Nioke Interview Questions_java dark horse notes

 Table of contents

1 [Authentication Module] Requirements Analysis

1.1 What is authentication and authorization

1.2 Business Process

1.2.1 Unified authentication

1.2.2 SSO single sign-on

1.2.3 Third-party certification

2 Spring Security Authentication and Authorization Framework

2.1 Introduction to Spring Security

2.2 Getting Started with Authentication and Authorization

2.2.1 Initialize the authentication module, LoginController

2.2.2 Certification test, import dependency, security management configuration class

2.2.3 Authorization test, configuration permissions

2.2.4 Spring Security principle and workflow

2.3 OAuth2 authentication protocol

2.3.1 WeChat scan code authentication login process

2.3.2 Application of OAuth2 in this project

2.3.3 Four authorization modes of OAuth2

2.3.3.1 Authorization code mode

2.3.3.2 Authorization Code Mode Test

2.3.3.3 Password mode

2.3.3.4 Application method of this project: authorization code and password

2.4 JWT

2.4.1 The problem of low performance of ordinary tokens

2.4.2 JWT introduction, stateless authentication, symmetric encryption and asymmetric encryption

2.4.3 jwt vs. session

2.4.4 Modify the token configuration class and test to generate JWT tokens

2.4.5 [Content Module] Import authentication dependencies and configuration classes

2.4.6 httpclient test, carry token to access resource service 

2.4.7 Code test, SecurityContextHolder class obtains user identity

3 Gateway Authentication

3.1 Certification process 

3.2 [Gateway module] The gateway uniformly verifies jwt and maintains the whitelist


1 [Authentication Module] Requirements Analysis

1.1 What is authentication and authorization

Up to now, the project has completed the course publishing function. After the course is released, users can learn by ordering videos on the online learning page. How to record the learning process of students? If you want to know the student's learning situation, you need to know the user's identity information, record which user learns which course at what time , and if the user wants to purchase a course, you also need to know the user's identity information. Therefore, the most basic thing to manage the learning process of students is to realize the user's identity authentication.

The authentication and authorization module realizes the identity authentication and user authorization functions of all users of the platform.

What is user authentication?

        User identity authentication means that when a user accesses system resources, the system requires verification of the user's identity information , and the user can continue to access if the identity is legal. Common forms of user identity authentication include: user name and password login, WeChat scanning code, etc.

The project includes three types of users: students, teachers of learning institutions, and platform operators. No matter which type of user accesses the protected resources of the project, identity authentication is required. For example: to publish a course operation, the teacher of the learning institution must first log in to the system successfully, and then execute the course release operation. To create an order, the student user must first log in to the system before creating an order. As shown below:

What is user authorization?

        After the user passes the authentication to access system resources, the system will judge whether the user has the permission to access the resources , and only allow access to the system resources with permission, and the resources without permission will not be accessible. This process is called user authorization. For example, when a user publishes a course, the system first authenticates the user. After passing the authentication, it continues to judge whether the user has the permission to publish courses. If the user does not have permission, it refuses to continue to access the system. As shown below:

1.2 Business Process

1.2.1 Unified authentication

The project includes three types of users: students, teachers of learning institutions, and platform operators. The three types of users will use a unified authentication entrance , as shown in the figure below:

The user enters the account number and password to submit the authentication, and the operation continues if the authentication passes.

The unified authentication service of the project accepts the user's authentication request, as shown in the figure below:

Through authentication, the authentication service issues a token to the user , which is equivalent to a pass to access the system, and the user takes the token to access the resources of the system.

1.2.2 SSO single sign-on

This project is built on the basis of micro-service architecture. Micro-services include: content management services, media asset management services, learning center services, system management services, etc. In order to improve user experience, users only need to authenticate once to access multiple System access, this function is called single sign-on.

Single Sign On (SSO), referred to as SSO, is currently one of the more popular enterprise business integration solutions. The definition of SSO is that in multiple application systems, users only need to log in once to access all mutually trusted application systems.

As shown in the figure below, users only need to authenticate once to access in multiple systems with access rights:

1.2.3 Third-party certification

In order to improve the user experience, many websites have the function of scanning code login, such as: WeChat scanning code login, QQ scanning code login, etc. The advantage of scanning the code to log in is that the user does not need to enter the account number and password, and the operation is simple. Another advantage is that it is conducive to the sharing of user information. The advantage of the Internet is resource sharing. Users are also a kind of resource. It is very difficult. If wechat scanning code login is provided, the cost of user registration will be saved, which is a very effective means of promotion.

The principle of WeChat scan code login is the use of third-party authentication, as shown in the figure below:

2 Spring Security Authentication and Authorization Framework

2.1 Introduction to Spring Security

The authentication function is a function that almost every project must have, and it has nothing to do with business. There are many authentication frameworks on the market, such as: Apache Shiro, CAS, Spring Security, etc. Since this project is built on the basis of Spring Cloud technology, Spring Security is a part of the spring family and is well integrated with Spring Cloud, so this project chooses Spring Security as the technical framework of the authentication service.

Spring Security is a powerful and highly customizable authentication and access control framework , which is a framework that focuses on providing authentication and authorization for Java applications.

Spring Security project home page: https://spring.io/projects/spring-security

Spring cloud Security: https://spring.io/projects/spring-cloud-security

2.2 Getting Started with Authentication and Authorization

2.2.1 Initialize the authentication module, LoginController

Next, we use the Spring Security framework to quickly build an authentication and authorization function system.

1. Deploy the authentication service project

Create xuecheng-plus-auth project

This project is an ordinary spring boot project that can connect to the database.

This project does not have the function of authentication and authorization .

2. Create a database

Create xc_users database

Import the xcplus_users.sql script in the course materials.

configuration 

Add auth-service-dev.yaml in nacos:

server:
  servlet:
    context-path: /auth
  port: 63070
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.101.65:3306/xc1010_users?serverTimezone=UTC&userUnicode=true&useSSL=false&
    username: root
    password: mysql

bootstrap.yml:

spring:
  application:
    name: auth-service
  cloud:
    nacos:
      server-addr: 192.168.101.65:8848
      discovery:
        namespace: dev402
        group: xuecheng-plus-project
      config:
        namespace: dev402
        group: xuecheng-plus-project
        file-extension: yaml
        refresh-enabled: true
        shared-configs:
          - data-id: swagger-${spring.profiles.active}.yaml
            group: xuecheng-plus-common
            refresh: true
          - data-id: logging-${spring.profiles.active}.yaml
            group: xuecheng-plus-common
            refresh: true
          - data-id: feign-${spring.profiles.active}.yaml
            group: xuecheng-plus-common
            refresh: true

  profiles:
    active: dev

The initial content of the logged-in controller

The initial project comes with a Controller class, as follows:

package com.xuecheng.auth.controller;


/**
 * @author Mr.M
 * @version 1.0
 * @description 测试controller
 * @date 2022/9/27 17:25
 */
@Slf4j
@RestController
public class LoginController {

  @Autowired
  XcUserMapper userMapper;

  @RequestMapping("/login-success")
  public String loginSuccess(){

      return "登录成功";
  }


  @RequestMapping("/user/{id}")
  public XcUser getuser(@PathVariable("id") String id){
    XcUser xcUser = userMapper.selectById(id);
    return xcUser;
  }
//先不添加授权,用于测试
  @RequestMapping("/r/r1")
  public String r1(){
    return "访问r1资源";
  }
//    @RequestMapping("/r/r1")
//    @PreAuthorize("hasAuthority('p1')")//拥有p1权限方可访问
//    public String r1() {
//        return "访问r1资源";
//    }
  @RequestMapping("/r/r2")
  public String r2(){
    return "访问r2资源";
  }



}

Start the project and try to access http://localhost:63070/auth/r/r1:

Access user information: http://localhost:63070/auth/user/52

All the above tests are normal, indicating that the project deployment is successful.

Because SpringSecurity has not been imported, you can directly access the page without being authenticated

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

2.2.2 Certification test, guide dependency, security management configuration class

1. Import dependencies 

Next, integrate Spring security with the auth authentication project, and add the dependencies required by Spring Security to pom.xml

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

Restart the project, visit http://localhost:63070/auth/r/r1

Automatically enter the /login login page. /login is provided by spring security . You don’t need to develop it. There are several css styles on this page that will load a little slower, as shown in the figure below:

user table:

What is the account number and password? The next step requires security configuration.

2. Create a security management configuration class

WebSecurityConfig.java to config:

Three parts are required:

1. User information

Configure two users in memory: zhangsan, lisi

zhangsan user has the authority of p1

Lisi user has the authority of p2

2. Password method

Temporary in plaintext

3. Security interception mechanism

Requests starting with /r/** require authentication

Log in successfully to the success page

code show as below:

 Temporarily use the plaintext method, only for testing

/**
 * @author Mr.M
 * @version 1.0
 * @description 安全管理配置
 * @date 2022/9/26 20:53
 */
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//配置用户信息服务
@Bean
public UserDetailsService userDetailsService() {
    //这里配置用户信息,这里暂时使用这种方式将用户存储在内存中
    InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
            manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
    manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
    return manager;
}

    @Bean
    public PasswordEncoder passwordEncoder() {
        //密码为明文方式
        return NoOpPasswordEncoder.getInstance();
    }

    //配置安全拦截机制
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/r/**").authenticated()//访问/r开始的请求需要认证通过
                .anyRequest().permitAll()//其它请求全部放行
                .and()
                .formLogin().successForwardUrl("/login-success");//登录成功跳转到/login-success
                http.logout().logoutUrl("/logout");//退出地址
    }
}

3. Restart the project and test

0. Login: Visit http://localhost:63070/login to log in "zhangsan" and "123"

1. Access user: Visit http://localhost:63070/auth/user/52 to normally access the user whose id is 52

2. Authentication test: Visit http://localhost:63070/auth/r/r1 to display the login page

The account is zhangsan, and the password is 123. If the password entered is incorrect, the authentication will fail. If the password is entered correctly, the login will be successful.

Why can /auth/user/52 be accessed normally, but the login page is displayed when visiting /auth/r/r1?

Because the logout page "http.logout().logoutUrl("/logout");" is configured in the login controller, you can log out by visiting /logout after successful authentication.

2.2.3 Authorization test, configuration permissions

User authentication is controlled by spring security when accessing system resources, to determine whether the user has access to the resource, if yes, continue to access, if not, deny access.

The following test authorization function:

1. Security management configuration class, which configures all user permissions.

In the WebSecurityConfig class, configure zhangsan to have p1 permission, and lisi to have p2 permission.

    @Bean
    public UserDetailsService userDetailsService() {
        //这里配置用户信息,这里暂时使用这种方式将用户存储在内存中
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
        manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
        return manager;
    }

2. LoginController specifies the relationship between resources and permissions.

What are system resources?

For example: to query a user's information, the user information is the resource of the system. To access the resource, you need to pass the URL, so each http interface we define in the controller is the interface for accessing the resource.

Next, configuring /r/r1 in the controller requires p1 permissions, and /r/r2 requires p2 permissions.

hasAuthority('p1') indicates that only those with p1 authority can access it.

code show as below:

@RestController
public class LoginController {
    ....
    @RequestMapping("/r/r1")
    @PreAuthorize("hasAuthority('p1')")//拥有p1权限方可访问
    public String r1(){
      return "访问r1资源";
    }
   
    @RequestMapping("/r/r2")
    @PreAuthorize("hasAuthority('p2')")//拥有p2权限方可访问
    public String r2(){
      return "访问r2资源";
    }
    ...

3. Now restart the project.

When accessing a url starting with /r/, it will judge whether the user is authenticated. If not, it will jump to the login page. If it has been authenticated, it will judge whether the user has the access right to the URL. If it has the access right to the URL, continue. Otherwise access is denied.

For example:

Visit /r/r1, automatically jump to the login page, and use zhangsan to log in to access normally, because the permission p1 is specified on the method of /r/r1, zhangsan user has permission p1, so it can be accessed normally.

Access /r/r1, use lisi to log in and deny access, because lisi user does not have permission p1 needs to deny access

Note: If @PreAuthorize is not added to the access, this method has no authorization control.

Corresponding authorization of Zhang San Li Si: 

The process of organizing authorization is shown in the figure below:

2.2.4 Spring Security principle and workflow

The problem that Spring Security solves is security access control , and the security access control function is actually to intercept all requests entering the system , and verify whether each request can access the resources it expects . According to the learning of the previous knowledge, it can be realized by technologies such as Filter or AOP. Spring Security's protection of web resources is realized by Filter , so start with this Filter and gradually deepen the principle of Spring Security.

        When Spring Security is initialized, a Servlet filter named SpringSecurityFilterChain will be created, of type org.springframework.security.web.FilterChainProxy, which implements javax.servlet.Filter, so external requests will pass through this class, as shown below Spring Security filter chain structure diagram:

FilterChainProxy is a proxy. What really works is the Filters contained in SecurityFilterChain in FilterChainProxy. At the same time, these Filters are managed by Spring as beans. They are the core of Spring Security and have their own responsibilities, but they do not directly handle user authentication . It does not directly handle user authorization , but hands them over to AuthenticationManager and AccessDecisionManager for processing.

The realization of spring Security function is mainly completed by a series of filter chains.

The following describes the main filters and their functions in the filter chain:

SecurityContextPersistenceFilter is the entry and exit of the entire interception process (that is, the first and last interceptor). It will obtain the SecurityContext from the configured SecurityContextRepository at the beginning of the request, and then set it to the SecurityContextHolder. After the request is completed, save the SecurityContext held by the SecurityContextHolder to the configured SecurityContextRepository, and clear the SecurityContext held by the securityContextHolder;

UsernamePasswordAuthenticationFilter is used to handle authentication from form submissions. The form must provide the corresponding user name and password, and there are also AuthenticationSuccessHandler and AuthenticationFailureHandler for processing after successful or failed login, which can be changed according to requirements;

FilterSecurityInterceptor is used to protect web resources, using AccessDecisionManager to authorize access to the current user, which has been introduced in detail earlier;

ExceptionTranslationFilter can catch all exceptions from FilterChain and handle them. But it will only handle two types of exceptions: AuthenticationException and AccessDeniedException, and it will continue to throw other exceptions.

The execution flow of Spring Security is as follows:

  1. The username and password submitted by the user are obtained by the username and password filter UsernamePasswordAuthenticationFilter in the SecurityFilterChain , and encapsulated as request Authentication , which is usually the implementation class of UsernamePasswordAuthenticationToken.
  2. Then the filter submits the Authentication to the Authentication Manager (AuthenticationManager) for authentication
  3. After the authentication is successful, the AuthenticationManager identity manager returns an Authentication instance filled with information (including the permission information, identity information, and details mentioned above, but the password is usually removed) through the DaoAuthentication Provider query.
  4. The security context container SecurityContextHolder will fill the Authentication with the information in step 3, and save the Authentication to the security context through the SecurityContextHolder.getContext().setAuthentication(…) method .
  5. It can be seen that the AuthenticationManager interface (authentication manager) is the core interface related to authentication and the starting point for initiating authentication. Its implementation class is ProviderManager. Spring Security supports multiple authentication methods, so ProviderManager maintains a List<AuthenticationProvider> list, storing multiple authentication methods, and the final actual authentication work is done by AuthenticationProvider. We know that the corresponding AuthenticationProvider implementation class of the web form is DaoAuthenticationProvider, and it maintains a UserDetailsService inside which is responsible for obtaining UserDetails. Finally, AuthenticationProvider fills UserDetails into Authentication.

2.3 OAuth2 authentication protocol

2.3.1 WeChat scan code authentication login process

WeChat scan code authentication is a third-party authentication method based on the OAuth2 protocol.

The OAUTH protocol provides a safe, open and simple standard for authorization of user resources . At the same time, any third party can use the OAUTH authentication service , and any service provider can implement its own OAUTH authentication service, so OAUTH is open.

The industry provides a variety of implementations of OAUTH such as PHP, JavaScript, Java, Ruby and other language development kits, which greatly saves programmers' time, so OAuth is simple. Many Internet services such as Open API, and many large companies such as Google, Yahoo, and Microsoft provide OAUTH authentication services. These are enough to show that the OAUTH standard has gradually become the standard for open resource authorization.

The Oauth protocol has been developed to version 2.0. Version 1.0 is too complicated, and version 2.0 has been widely used.

Reference: oAuth_Baidu Encyclopedia

Oauth协议:RFC 6749: The OAuth 2.0 Authorization Framework

The process of WeChat authentication scan code login: 

Let’s analyze an example of Oauth2 authentication, the dark horse programmer website uses WeChat authentication to scan the code to log in:

1. The user clicks on WeChat to scan the code

The user enters the login page of the dark horse program, clicks the WeChat icon to open the WeChat code scanning interface.

A QR code pops up.

The purpose of WeChat scanning code is to log in to the official website of Dark Horse Programmer through WeChat authentication. The Dark Horse Programmer website needs to obtain the identity information of the current user from WeChat to allow the current user to successfully log in to the Dark Horse website.

Now figure out a few concepts:

Resources: user information, stored in WeChat.

Resource owner: The user is the owner of user information resources.

Authentication service: WeChat is responsible for authenticating the identity of the current user and issuing tokens for the client.

Client: The client will carry the token to request WeChat to obtain user information. The dark horse programmer website is the client, and the dark horse website needs to be opened in a browser.

2. The user authorizes the dark horse website to access user information

The resource owner scans the QR code to indicate that the resource owner requests WeChat for authentication, and WeChat authentication returns the authorization page to the user's mobile phone, as shown in the figure below:

Ask the user whether to authorize the dark horse programmer to access their user information on WeChat, the user clicks "Confirm Login" to agree to the authorization, and the WeChat authentication server will issue an authorization code to the dark horse programmer's website.

Only when the resource owner agrees to WeChat can the dark horse website be allowed to access the resource.

3. The website of the dark horse programmer obtains the authorization code

4. Bring the authorization code to request the WeChat authentication server to apply for a token

This interaction is invisible to the user.

5. The WeChat authentication server responds with a token to the website of the dark horse programmer

This interaction is invisible to the user.

6. The dark horse programmer website carries a token to request the WeChat resource server to obtain resources, that is, user information.

7. The resource server returns the protected resource, namely user information

8. The dark horse website receives user information, and the user logs in successfully on the dark horse website at this time.

After understanding the process of scanning the WeChat code to log in to the dark horse website, the next step is to understand the authentication process of Oauth2.0, as follows:

Quoted from the Oauth2.0 protocol rfc6749 RFC 6749: The OAuth 2.0 Authorization Framework

Oauth2 includes the following roles:

1. Client

It does not store resources itself, and needs to request the resources of the resource server through the authorization of the resource owner, such as: mobile client, browser, etc.

The dark horse website in the above example is the client, and it needs to be opened through a browser.

2. Resource owner

Resource: User Information

Usually a user, but also an application, that is, the owner of the resource.

A indicates that the client requests authorization from the resource owner.

B means that the resource owner authorizes the client, the dark horse website, to access its own user information.

3. Authorization server (also called authentication server)

For example, WeChat server. The authentication server authenticates the resource owner and also authenticates the client and issues tokens.

The C client, namely the dark horse website, carries the authorization code to request authentication.

D authenticates by issuing a token.

4. Resource server

Such as database. A server that stores resources.

E means that the client, i.e. the dark horse website, carries the token and requests the resource server to obtain resources.

F indicates that the resource server provides protected resources after the verification token passes.

2.3.2 Application of OAuth2 in this project

Oauth2 is a standard open authorization protocol . Applications can use Oauth2 according to their own requirements. This project uses Oauth2 to achieve the following goals:

1. Xuecheng accesses the resources of third-party systems (such as WeChat) online.

This project needs to access WeChat scan code login, so this project needs to use the OAuth2 protocol to access user information in WeChat .

2. The external system accesses the resources of Xuecheng Online.

Similarly, when a third-party system wants to access the resources of the Xuecheng Online website, it can also be based on the OAuth2 protocol.

3. Xuecheng Online front-end (client) Access the resources of Xuecheng Online microservices.

This project is a front-end and back-end separation architecture, and the front-end access to microservice resources can also be authenticated based on the OAuth2 protocol.

2.3.3 Four authorization modes of OAuth2

Spring Security supports OAuth2 authentication. OAuth2 provides four authorization modes : authorization code mode, password mode, simplified mode, and client mode. The example of WeChat scan code login mentioned above is based on the authorization code mode. Among these four modes, the authorization code mode And the password mode is widely used.

This section uses Spring Security to demonstrate the authorization code mode and password mode. Please refer to the relevant information for the other two.

2.3.3.1 Authorization code mode

The example of WeChat scan code login mentioned above is based on the authorization code mode. 

Several authorization modes of OAuth2 are to obtain tokens in different ways according to different application scenarios . The ultimate goal is to obtain tokens issued by authentication services, and finally obtain resources through tokens .

The simple understanding of the authorization code mode is to use the authorization code to obtain the token . If you want to obtain the token, you must first obtain the authorization code. The acquisition of the authorization code requires the authorization and consent of the resource owner to obtain it.

The following figure is an interactive diagram of the authorization code mode:

Also take the dark horse website WeChat scan code login as an example to illustrate:

1. The user opens the browser.

2. Access the website of the client, namely Dark Horse, through a browser.

3. The user requests authorization from the authentication service through the browser, and the URL of the client will be carried when requesting authorization. This URL is the redirection address for issuing the authorization code.

4. The authentication service returns an authorization page to the resource owner.

5. The resource owner personally authorizes and agrees.

6. Send the authorization agreement to the authentication service through the browser.

7. The authentication service redirects to the client address and carries the authorization code.

8. The client, the dark horse website, receives the authorization code.

9. The client carries the authorization code to apply for a token from the authentication service.

10. The authentication service issues a token to the client.

2.3.3.2  Authorization code mode test

To test the authorization mode, you must first configure the authorization server, that is, the authentication server in the above figure, and you need to configure the authorization service and token policy.

1. Copy the authorization service configuration class AuthorizationServer.java and the token policy configuration class TokenConfig.java from the course materials to the config package of the authentication service.

Description : AuthorizationServer is marked with @EnableAuthorizationServer annotation and inherits AuthorizationServerConfigurerAdapter to configure OAuth2.0 authorization server.

package com.xuecheng.auth.config;
*/
 @Configuration
 @EnableAuthorizationServer
 public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
 ...

The parent class AuthorizationServerConfigurerAdapter requires the configuration of the following classes:

public class AuthorizationServerConfigurerAdapter implements AuthorizationServerConfigurer {
    public AuthorizationServerConfigurerAdapter() {}
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {}
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {}
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {}
}

1 ) ClientDetailsServiceConfigurer : used to configure the client details service (ClientDetailsService),

Can any client access its authentication service at will? The answer is no, the service provider will give the client who is approved for access an identity, which is used as a credential for access, including the client ID and client secret key, and configure the detailed information of the client who is approved for access here.

2 ) AuthorizationServerEndpointsConfigurer : Used to configure the access endpoints and token services of the token (token).

3 ) AuthorizationServerSecurityConfigurer : Used to configure the security constraints of the token endpoint.

2. TokenConfig is a token policy configuration class

Temporarily use InMemoryTokenStore to store tokens in memory first, and configure information such as the validity period of tokens as follows:

    //令牌管理服务
    @Bean(name="authorizationServerTokenServicesCustom")
    public AuthorizationServerTokenServices tokenService() {
        DefaultTokenServices service=new DefaultTokenServices();
        service.setSupportRefreshToken(true);//支持刷新令牌
        service.setTokenStore(tokenStore);//令牌存储策略
        service.setAccessTokenValiditySeconds(7200); // 令牌默认有效期2小时
        service.setRefreshTokenValiditySeconds(259200); // 刷新令牌默认有效期3天
        return service;
    }

3. Configure the authentication management bean

@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    ....

Restart the authentication service

1. get request to obtain the authorization code

地址: http://localhost:63070/auth/oauth/authorize?client_id=XcWebApp&response_type=code&scope=all&redirect_uri=http://www.51xuecheng.cn

The parameter list is as follows:

  • client_id: Client access ID.
  • response_type: The authorization code mode is fixed as code.
  • scope: client authority.
  • redirect_uri: redirect uri, when the authorization code application is successful, it will jump to this address, and the code parameter (authorization code) will be attached behind it.

Enter account zhangsan, password 123 to log in successfully, enter /oauth/authorize?client_id=XcWebApp&response_type=code&scope=all&redirect_uri=http://www.51xuecheng.cn

Show authorization page

Authorize "XcWebApp" to access its own protected resources?

Choose Agree.

2. If the request is successful , redirect to http://www.51xuecheng.cn/?code=authorization code, for example: http://www.51xuecheng.cn/?code=Wqjb5H

3. Use the httpclient tool to send a post request to apply for a token

### 授权码模式
### 第一步申请授权码(浏览器请求)/oauth/authorize?client_id=c1&response_type=code&scope=all&redirect_uri=http://www.51xuecheng.cn
### 第二步申请令牌
POST {
   
   {auth_host}}/auth/oauth/token?client_id=XcWebApp&client_secret=XcWebApp&grant_type=authorization_code&code=CTvCrB&redirect_uri=http://www.51xuecheng.cn

The parameter list is as follows

  • client_id: Client access ID.
  • client_secret: client secret key.
  • grant_type: authorization type, fill in authorization_code, indicating the authorization code mode
  • code: Authorization code, which is the authorization code you just obtained. Note: the authorization code will be invalid after being used only once, and you need to apply again.
  • redirect_uri: The redirect url when applying for the authorization code must be consistent with the redirect_uri used when applying for the authorization code.

Applying for a token successfully looks like this:

{
  "access_改成自己的token": "368b1ee7-a9ee-4e9a-aae6-0fcab243aad2",
  "token_type": "bearer",
  "refresh_token": "3d56e139-0ee6-4ace-8cbe-1311dfaa991f",
  "expires_in": 7199,
  "scope": "all"
}

illustrate:

1. access_token, access token , used to access resources.

2. token_type, bearer is a token type defined in RFC6750. When carrying a token to access resources, it is necessary to add bearer space token content in the head

3. refresh_token, when the token is about to expire, the refresh token can be used to generate the token again.

4. expires_in: expiration time (seconds)

5. scope, the scope of authority of the token, the server can authorize the token according to the scope of authority of the token.

2.3.3.3 Password mode

The password mode is simpler than the authorization code mode. The authorization code mode requires a browser for the user to authorize himself, and the password mode does not need a browser, as shown in the following figure:

1. The resource owner provides the account and password

2. The client applies for a token from the authentication service , and the request carries the account number and password

3. The authentication service verifies that the account number and password are correct and issues the token .

start testing:

1. POST request to get the token

/oauth/token?client_id=XcWebApp&client_secret=XcWebApp&grant_type=password&username=shangsan&password=123

The parameter list is as follows:

  • client_id: Client access ID.
  • client_secret: client secret key.
  • grant_type: authorization type, fill in password to indicate the password mode
  • username: Username of the resource owner.
  • password: Resource owner password.

2. The authorization server sends the token (access_token) to the client

Test with httpclient

### 密码模式
POST {
   
   {auth_host}}/auth/oauth/token?client_id=XcWebApp&client_secret=XcWebApp&grant_type=password&username=zhangsan&password=123

Return example:

{
  "access_t改成自己的oken": "368b1ee7-a9ee-4e9a-aae6-0fcab243aad2",
  "token_type": "bearer",
  "refresh_token": "3d56e139-0ee6-4ace-8cbe-1311dfaa991f",
  "expires_in": 6806,
  "scope": "all"
}

        This mode is very simple, but it means that the user's sensitive information is directly leaked to the client , so this means that this mode can only be used when the client is developed by ourselves.

2.3.3.4 Application method of this project: authorization code and password

By demonstrating the authorization code mode and password mode, the authorization code mode is suitable for the situation where the client and the authentication service are not in the same system, so this project uses the authorization code mode to complete WeChat code scanning authentication. This project uses the password mode as the authentication method for the front-end to request microservices.

2.4 JWT

2.4.1 The problem of low performance of ordinary tokens

Ordinary tokens need to be verified by "resource service remote call authentication service", and the performance is low. 

The client applies for the token, and then the client carries the token to access the resource, and the resource server will verify the legitimacy of the token.

How does the resource server verify the legitimacy of the token?

Let's take the password mode of OAuth2 as an example to illustrate:

Instructions from step 4:

1. The client carries the token to access the resource service to obtain resources.

2. The resource service remotely requests the authentication service to verify the legitimacy of the token

3. If the token is valid, the resource service returns the resource to the client.

There is a problem here:

That is, the verification token needs to request the authentication service remotely, and each access of the client will be verified remotely , and the execution performance is low .

The resource service verifies the token itself 

If the resource service can verify the legitimacy of the token by itself, the cost of remotely requesting the authentication service will be saved and the performance will be improved. As shown below:

How to solve the above problem and realize the resource service to verify the token by itself .

The above problem can be solved by using the JWT format of the token. After the user passes the authentication, a JWT token will be obtained. The JWT token already includes user-related information. The client only needs to carry the JWT to access the resource service. The resource service is based on the prior agreement . The algorithm of the token completes the token verification by itself, and there is no need to request the authentication service to complete the authorization every time.

2.4.2 JWT introduction, stateless authentication, symmetric encryption and asymmetric encryption

JSON Web Token (JWT) is a network token technology that uses JSON format to transfer data . It is an open industry standard (RFC 7519), which defines a concise, self-contained protocol format for communication The two parties pass the json object, and the information passed can be verified and trusted after digital signature. It can use the HMAC algorithm or use the RSA public key/private key pair to sign to prevent content tampering. Official website: JSON Web Tokens - jwt.io

Stateless authentication can be achieved using JWT.

Stateful authentication:

The server saves the client information. The server does not need to carry user information for each request.

The traditional session-based method is stateful authentication. After a successful user login, the user's identity information is stored on the server in the form of a session , which increases the storage pressure on the server, and this method is not suitable for application in distributed systems.

As shown in the figure below, when a user accesses an application service, each application service will go to the server to check the session information. If the user does not exist in the session, it means that the user has not logged in. At this time, re-authentication will be performed. The solution to this problem is Session copy , Session paste.

stateless authentication 

The server does not store client information. The server needs to carry user information every time it requests. 

If the authentication is implemented in a distributed system based on token technology, the server does not need to store the session , but can store the user identity information in the token . After the user is authenticated, the authentication service issues a token to the user, and the user stores the token in the client When accessing the application service, carry the token to access, and the server parses out the user information from the jwt . This process is known as stateless authentication.

session:

Because the session is stored on the server, multi-machine data sharing needs to be realized in a distributed environment. The session generally needs to be combined with cookies to implement authentication, so the browser needs to support cookies, so the mobile terminal cannot use the session authentication scheme.

Advantages of JWT tokens:

1. jwt is based on json, which is very convenient for parsing.

2. Rich content can be customized in the token, which is easy to expand.

3. Through asymmetric encryption algorithm and digital signature technology, JWT prevents tampering and has high security .

4. Resource services can use JWT to complete authorization without relying on authentication services .

shortcoming:

The JWT token is longer and takes up a lot of storage space.

Below is an example of a JWT token:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzMSJdLCJ1c2VyX25hbWUiOiJ6aGFuZ3NhbiIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE2NjQyNTQ2NzIsImF1dGhvcml0aWVzIjpbInAxIl0sImp0aSI6Ijg4OTEyYjJkLTVkMDUtNGMxNC1iYmMzLWZkZTk5NzdmZWJjNiIsImNsaWVudF9pZCI6ImMxIn0.wkDBL7roLrvdBG2oGnXeoXq-zZRgE9IVV2nxd-ez_oA

The JWT token consists of three parts, each part is separated by a dot (.), for example: xxxxx.yyyyy.zzzzz

Header       

  The header includes the type of token (ie JWT) and the hashing algorithm used (eg HMAC SHA256 or RSA)

  An example is as follows:

  Below is the content of the Header section

   {
    "alg": "HS256",
    "typ": "JWT"
  }

  Use Base64Url to encode the above content, and get a string that is the first part of the JWT token.

 Payload

  The second part is the load, and the content is also a json object. It is the place to store valid information. It can store the information fields provided by jwt, such as: iss (issuer), exp (expiration timestamp), sub (user-oriented) etc. You can also customize fields.

  This section is not recommended to store sensitive information, because this section can decode and restore the original content.

  Finally, encode the second part of the payload with Base64Url to get a string that is the second part of the JWT token.

  one example:

  {
    "sub": "1234567890",
    "name": "456",
    "admin": true
  }

 Signature

  The third part is the signature , which is used to prevent the jwt content from being tampered with .

  This part uses base64url to encode the first two parts. After encoding, use dots (.) to connect to form a string, and finally use the signature algorithm declared in the header to sign.

  one example:

  HMACSHA256(
    base64UrlEncode(header) + "." +
    base64UrlEncode(payload),
    secret)

base64UrlEncode(header): The first part of the jwt token.

base64UrlEncode(payload): The second part of the jwt token.

secret: The secret key used for signing.

Why JWT can prevent tampering?

As long as the key is not leaked, jwt will not be tampered with. 

The third part of JWT uses a signature algorithm to sign the contents of the first and second parts. The commonly used signature algorithm is HS256, and md5, sha, etc. are common. The signature algorithm needs to use a key for signing, and the key is not disclosed to the public. , and the signature is irreversible. If a third party changes the content, the server will fail to verify the signature. To ensure that the verification signature is correct, the content and key must be consistent with those before the signature.

Symmetric encryption

From the above figure, we can see that the authentication service and the resource service use the same key , which is called symmetric encryption , and the symmetric encryption is efficient. The disadvantage is that once the key is leaked, the jwt token can be forged.

asymmetric encryption 

JWT can also use asymmetric encryption. The authentication service keeps the private key itself , and sends the public key to trusted clients and resource services. The public key and private key are paired , and the paired public key and private key can work normally. Encryption and decryption, asymmetric encryption is inefficient but more secure than symmetric encryption .

2.4.3  jwt vs. session

Authentication process
session-based authentication process

The user enters the user name and password in the browser, and the server generates a session after passing the password verification and saves it to the database

The server generates a sessionId for the user, and places a cookie with the sessionId in the user's browser, which will be accessed with this cookie information in subsequent requests

The server obtains the cookie, and finds the database by obtaining the sessionId in the cookie to determine whether the current request is valid

JWT-based authentication process

The user enters the user name and password in the browser, and the server generates a token after passing the password verification and saves it in the database

The front end obtains the token, stores it in a cookie or local storage, and accesses with this token information in subsequent requests

The server obtains the token value, and judges whether the current token is valid by searching the database

Advantages and disadvantages
JWT is stored on the client, and no additional work is required in a distributed environment. Since the session is stored on the server side, multi-machine data sharing needs to be realized in a distributed environment. The session generally needs to be combined with cookies to implement authentication, so the browser needs to support cookies, so the mobile terminal cannot use the session authentication scheme.

Security
The payload of the JWT is base64 encoded, so sensitive data cannot be stored in the JWT. The session information is stored on the server side, which is relatively safer

Performance
After encoding, the JWT will be very long. The limit size of the cookie is generally 4k, and the cookie may not fit, so the JWT is generally placed in the local storage. And every http request made by the user in the system will carry the JWT in the Header, and the Header of the HTTP request may be larger than the Body. The sessionId is just a short string, so the HTTP request using JWT is much more expensive than using session

One-time
statelessness is the characteristic of JWT, but it also leads to this problem. JWT is one-time. If you want to modify the content inside, you must issue a new JWT.

Cannot be discarded
Once a JWT is issued, it will remain valid until it expires and cannot be discarded halfway. If you want to discard, a common processing method is to combine redis

Renewal
If you use JWT for session management, the traditional cookie renewal scheme is generally built into the framework. The session is valid for 30 minutes. If there is an access within 30 minutes, the validity period will be refreshed to 30 minutes. In the same way, to change the valid time of JWT, a new JWT must be issued.

The easiest way is to refresh the JWT every time a request is made, that is, each HTTP request returns a new JWT. This method is not only violent and not elegant, but also requires JWT encryption and decryption for each request, which will cause performance problems. Another method is to set the expiration time for each JWT separately in redis, and refresh the expiration time of JWT every time you visit

Choosing JWT or session
JWT has many disadvantages, but in a distributed environment, it does not need to implement additional multi-machine data sharing like session, although multi-machine data sharing of seesion can be done through sticky session, session sharing, session replication, persistent session, terracoa Implement various mature solutions such as seesion replication to solve this problem. But JWT requires no extra work, isn't it good to use JWT? And the one-time shortcoming of JWT can be compensated by combining redis
 

2.4.4 Modify the token configuration class and test to generate JWT tokens

In the token configuration class in the authentication service , configure the jwt token service to generate a token in jwt format.

package com.xuecheng.auth.config;

@Configuration
public class TokenConfig {

//定义密钥
    private String SIGNING_KEY = "mq123";

    @Autowired
    TokenStore tokenStore;

//    @Bean
//    public TokenStore tokenStore() {
//        //使用内存存储令牌(普通令牌)
//        return new InMemoryTokenStore();
//    }

    @Autowired
    private JwtAccessTokenConverter accessTokenConverter;

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(SIGNING_KEY);
        return converter;
    }

    //令牌管理服务
    @Bean(name="authorizationServerTokenServicesCustom")
    public AuthorizationServerTokenServices tokenService() {
        DefaultTokenServices service=new DefaultTokenServices();
        service.setSupportRefreshToken(true);//支持刷新令牌
        service.setTokenStore(tokenStore);//令牌存储策略

        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter));
        service.setTokenEnhancer(tokenEnhancerChain);

        service.setAccessTokenValiditySeconds(7200); // 令牌默认有效期2小时
        service.setRefreshTokenValiditySeconds(259200); // 刷新令牌默认有效期3天
        return service;
    }
}

Restart the authentication service.

Use httpclient to apply for a token through password mode

### 密码模式
POST {
   
   {auth_host}}/oauth/token?client_id=XcWebApp&client_secret=XcWebApp&grant_type=password&username=zhangsan&password=123

An example of generating jwt is as follows:

{
  "access改成自己的_token": "eyJhbXxx.-9SKI-qUqKhKcs8Gb80Rascx-JxqsNZxxXoPo82d8SM",
  "token_type": "bearer",
  "refresh_token": "eyJhbXxx.eyJhdXxx.Wsw1JXxxx",
  "expires_in": 7199,
  "scope": "all",
  "jti": "e9d3d0fd-24cb-44c8-8c10-256b34f8dfcc"
}

1. access_token, the generated jwt token, used to access resources.

2. token_type, bearer is a token type defined in RFC6750. When carrying jwt to access resources, it is necessary to add bearer jwt token content in the head

3. refresh_token, when the jwt token is about to expire, use the refresh token to generate the jwt token again.

4. expires_in: expiration time (seconds)

5. scope, the scope of authority of the token, the server can authorize the token according to the scope of authority of the token.

6. jti: the unique identifier of the token.

Verify jwt token

We can verify the jwt token through the check_token interface that comes with Spring Security, just add "/check_token" to the path 

###校验jwt令牌
POST {
   
   {auth_host}}/oauth/check_token?token=eyJhbGciOXxx.qy46CXxx

The parsed user information is as follows:


{
  "aud": [
    "res1"
  ],
  "user_name": "zhangsan",
  "scope": [
    "all"
  ],
  "active": true,
  "exp": 1664371780,
  "authorities": [
    "p1"
  ],
  "jti": "f0a3cdeb-399d-48f0-8804-eca638ad8857",
  "client_id": "c1"
}

2.4.5 [Content Module] Import authentication dependencies and configuration classes

After getting the jwt token, the next step is to carry the token to access the resources in the resource service. Each microservice of this project is the resource service, such as: content management service, the client applies for the jwt token, and carries the jwt to the content management service to query Course information. At this time, the content management service needs to verify the jwt. Only when the jwt is valid can the access be continued. As shown below:

1. Add dependencies to the content-api project of the content management service

<!--认证相关-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

2. Create a configuration class

Create a resource service configuration class ResouceServerConfig and a token configuration class TokenConfig under the config package of the content management api project.

/**
 * @description 资源服务配置
 * @author Mr.M
 * @date 2022/10/18 16:33
 * @version 1.0
 */
 @Configuration
 @EnableResourceServer
 @EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
 public class ResouceServerConfig extends ResourceServerConfigurerAdapter {


  //资源服务标识
  public static final String RESOURCE_ID = "xuecheng-plus";

  @Autowired
  TokenStore tokenStore;

  @Override
  public void configure(ResourceServerSecurityConfigurer resources) {
   resources.resourceId(RESOURCE_ID)//资源 id
           .tokenStore(tokenStore)
           .stateless(true);
  }

 @Override
 public void configure(HttpSecurity http) throws Exception {
  http.csrf().disable()
          .authorizeRequests()
//需要认证的请求
//                .antMatchers("/r/**","/course/**").authenticated()//所有匹配的请求必须经过认证
          .anyRequest().permitAll()    //全部请求放行
  ;
 }

 }
@Configuration
public class TokenConfig {

//对称加密,和认真模块的令牌配置类的密钥相同
    String SIGNING_KEY = "mq123";


//    @Bean
//    public TokenStore tokenStore() {
//        //使用内存存储令牌(普通令牌)
//        return new InMemoryTokenStore();
//    }

    @Autowired
    private JwtAccessTokenConverter accessTokenConverter;

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(SIGNING_KEY);
        return converter;
    }


}

2.4.6 httpclient test, carry token to access resource service 

1. Temporarily configure the course path to be authenticated

To modify the content module first, the resource service configuration class ResouceServerConfig, the configuration course path needs to be authenticated:

2. Restart the content management service and use httpclient to test:

1. Access to query the course interface based on the course id

### 查询课程信息
GET http://localhost:63040/content/course/2

return:



{
  "error": "unauthorized",
  "error_description": "Full authentication is required to access this resource"
}

From the returned information, it can be seen that there is currently no authentication.

3. The following carries the JWT token access interface:

1. Apply for jwt token

Apply for a token in password mode.

### 密码模式
POST {
    
    {auth_host}}/auth/oauth/token?client_id=XcWebApp&client_secret=XcWebApp&grant_type=password&username=zhangsan&password=123

2. Access resource service address with jwt token

### 携带token访问资源服务
GET http://localhost:63040/content/course/2
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzMSJdLCJ1c2VyX25hbWUiOiJ6aGFuZ3NhbiIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE2NjQzMzM0OTgsImF1dGhvcml0aWVzIjpbInAxIl0sImp0aSI6IjhhM2M2OTk1LWU1ZGEtNDQ1Yy05ZDAyLTEwNDFlYzk3NTkwOSIsImNsaWVudF9pZCI6ImMxIn0.73eNDxTX5ifttGCjwc7xrd-Sbp_mCfcIerI3lGetZto

Add Authorization in the request header , the content is Bearer token, and Bearer is used to access resources through the oauth2.0 protocol.

If the jwt token is carried and the jwt is correct, the content of the resource service can be accessed normally.

If it is not correct, an error that the token is invalid will be reported:

{
  "error": "invalid_token",
  "error_description": "Cannot convert access token to JSON"
}

2.4.7 Code test, SecurityContextHolder class obtains user identity

 Provisional configuration course path needs to be authenticated

To modify the content module first, the resource service configuration class ResouceServerConfig, the configuration course path needs to be authenticated:

The user identity information is recorded in the jwt token. When the client carries the jwt to access the resource service, the resource service will restore the content of the first two parts after the verification of the resource service to retrieve the user's identity information, and put the user identity information in the SecurityContextHolder context , SecurityContext is bound to the current thread to facilitate obtaining user identity.

Also take the query course interface as an example, enter the code of the query course interface, and add the code to obtain the user identity

@ApiOperation("根据课程id查询课程基础信息")
@GetMapping("/course/{courseId}")
public CourseBaseInfoDto getCourseBaseById(@PathVariable("courseId") Long courseId){
    //取出当前用户身份。底层使用的ThreadLocal,把用户身份信息放到线程里
    Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    System.out.println(principal);
    return courseBaseInfoService.getCourseBaseInfo(courseId);
}

Note when testing:

1. First, specify the security interception mechanism in the resource service configuration. Requests beginning with /course/ need to be authenticated, that is, requests to the /course/{courseId} interface need to carry jwt tokens and pass the visa.

2. The authentication service generates a jwt token and writes the user identity information into the token. At present, the user information is hard-coded and temporarily stored in memory.

as follows:

@Bean
public UserDetailsService userDetailsService() {
    //这里配置用户信息,这里暂时使用这种方式将用户存储在内存中
    InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
    manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
    manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
    return manager;
}

3. We use the zhangsan information when we use the password mode to generate the jwt token, so the zhangsan information is stored in the jwt token, so the zhangsan information should be taken out in the resource service.

After clearing the above content, restart the content management service below to track whether the obtained user identity is correct.

3 gateway authentication

Review Gateway Responsibilities

Authentication: As a microservice entry, the gateway needs to verify whether the user is eligible for the request, and intercept it if not.

Routing and load balancing: All requests must first pass through the gateway, but the gateway does not process business, but forwards the request to a microservice according to certain rules. This process is called routing. Of course, when there are multiple target services for routing, load balancing is also required.

Current limiting: When the request traffic is too high, the gateway will release the request according to the speed that the downstream microservice can accept, so as to avoid excessive service pressure.

3.1 Certification process 

So far, the test has passed the authentication service to issue the jwt token, the client carries the jwt to access the resource service, and the resource service verifies the legitimacy of the jwt. As shown below:

Looking closely at this diagram, a very important component in the architecture of this project is missing: the gateway.

Add the gateway and complete the authentication flow chart:

All requests to access microservices must pass through the gateway. Authentication of user identity at the gateway can intercept many illegal requests outside the microservice, which is called gateway authentication.

Gateway Responsibilities:

1. Website whitelist maintenance

Allow all URLs that do not require authentication.

2. Verify the legitimacy of jwt.

In addition to the whitelist, the rest is the request that needs to be authenticated. The gateway needs to verify the legitimacy of the jwt. If the jwt is legal, it means that the user's identity is legal. Otherwise, it means that the identity is not legal and refuses to continue accessing.

The gateway is not responsible for authorization, only for authentication:

The gateway is not responsible for authorization, and the authorization operation for the request is performed in each microservice, because the microservice knows which interfaces the user has access to best.

3.2 [Gateway module] The gateway uniformly verifies jwt and maintains the whitelist

The following implements gateway authentication website whitelist maintenance and verifies the legitimacy of jwt:

1. Add dependencies to the gateway project

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
</dependency>

2. Import the configuration class to the config package of the gateway project.

These configuration classes are common:

GatewayAuthFilter
/**
 * @author Mr.M
 * @version 1.0
 * @description 网关认证过虑器
 * @date 2022/9/27 12:10
 */
@Component
@Slf4j
public class GatewayAuthFilter implements GlobalFilter, Ordered {


    //白名单
    private static List<String> whitelist = null;

    static {
        //加载白名单
        try (
                InputStream resourceAsStream = GatewayAuthFilter.class.getResourceAsStream("/security-whitelist.properties");
        ) {
            Properties properties = new Properties();
            properties.load(resourceAsStream);
            Set<String> strings = properties.stringPropertyNames();
            whitelist= new ArrayList<>(strings);

        } catch (Exception e) {
            log.error("加载/security-whitelist.properties出错:{}",e.getMessage());
            e.printStackTrace();
        }


    }

    @Autowired
    private TokenStore tokenStore;


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //请求的url
        String requestUrl = exchange.getRequest().getPath().value();
        AntPathMatcher pathMatcher = new AntPathMatcher();
        //白名单放行
        for (String url : whitelist) {
            if (pathMatcher.match(url, requestUrl)) {
                return chain.filter(exchange);
            }
        }

        //检查token是否存在
        String token = getToken(exchange);
        if (StringUtils.isBlank(token)) {
            return buildReturnMono("没有认证",exchange);
        }
        //判断是否是有效的token
        OAuth2AccessToken oAuth2AccessToken;
        try {
            oAuth2AccessToken = tokenStore.readAccessToken(token);

            boolean expired = oAuth2AccessToken.isExpired();
            if (expired) {
                return buildReturnMono("认证令牌已过期",exchange);
            }
            return chain.filter(exchange);
        } catch (InvalidTokenException e) {
            log.info("认证令牌无效: {}", token);
            return buildReturnMono("认证令牌无效",exchange);
        }

    }

    /**
     * 获取token
     */
    private String getToken(ServerWebExchange exchange) {
        String tokenStr = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (StringUtils.isBlank(tokenStr)) {
            return null;
        }
        String token = tokenStr.split(" ")[1];
        if (StringUtils.isBlank(token)) {
            return null;
        }
        return token;
    }




    private Mono<Void> buildReturnMono(String error, ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();
        String jsonString = JSON.toJSONString(new RestErrorResponse(error));
        byte[] bits = jsonString.getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }


    @Override
    public int getOrder() {
        return 0;
    }
}
SecurityConfig
 @EnableWebFluxSecurity
 @Configuration
 public class SecurityConfig {


  //安全拦截配置
  @Bean
  public SecurityWebFilterChain webFluxSecurityFilterChain(ServerHttpSecurity http) {

   return http.authorizeExchange()
           .pathMatchers("/**").permitAll()
           .anyExchange().authenticated()
           .and().csrf().disable().build();
  }


 }
TokenConfig
@Configuration
public class TokenConfig {

    String SIGNING_KEY = "mq123";


//    @Bean
//    public TokenStore tokenStore() {
//        //使用内存存储令牌(普通令牌)
//        return new InMemoryTokenStore();
//    }

    @Autowired
    private JwtAccessTokenConverter accessTokenConverter;

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(SIGNING_KEY);
        return converter;
    }


}

RestErrorResponse
/**
 * 错误响应参数包装
 */
public class RestErrorResponse implements Serializable {

    private String errMessage;

    public RestErrorResponse(String errMessage){
        this.errMessage= errMessage;
    }

    public String getErrMessage() {
        return errMessage;
    }

    public void setErrMessage(String errMessage) {
        this.errMessage = errMessage;
    }
}

3. Configure the whitelist file security-whitelist.properties

The content is as follows (continuously supplemented)

/auth/**=认证地址
/content/open/**=内容管理公开访问接口
/media/open/**=媒资管理公开访问接口

4. Test:

Restart the gateway project for testing

1) Apply for a token

2) Access resource services through the gateway

Access content management services here

### 通过网关访问资源服务
GET http://localhost:63010/content/course/2
Authorization: Bearer eyJXxx.eyJhdXxx.lOITXxx

When the token is correct, the resource service can be accessed normally. If the token verification fails, the returned token is invalid:

{
  "errMessage": "认证令牌无效"
}

5. Before the development of the authentication function, all paths are temporarily released

Note: After the gateway authentication function has been debugged, since the authentication function has not been developed yet, when the URL of the front-end request gateway is not in the middle of the white list, there will be a "no authentication" error. Temporarily add all release configurations to the white list, pending the development of the authentication function After completing, shield all the release configurations,

6. Comment out the authentication path in the configuration class in the content module

Since the token verification is performed at the gateway, the legitimacy of the token is no longer verified at the microservice, and the ResourceServerConfig class of the content management service is modified to block authenticated().

 @Override
 public void configure(HttpSecurity http) throws Exception {
  http.csrf().disable()
          .authorizeRequests()
//          .antMatchers("/r/**","/course/**").authenticated()//所有/r/**的请求必须认证通过
          .anyRequest().permitAll()
  ;
 }

Guess you like

Origin blog.csdn.net/qq_40991313/article/details/130093971