SpringSecurity4 learning notes

Posted by dough boy on Fri, 21 Jan 2022 18:29:55 +0100

Structure forming

1. SpringSecurity

2. Introductory cases

Create a new SpringBoot project and add Security and Web dependencies

Create a new login and home page in resources – static

Start the project and visit localhost:8080/login to see the login page that appears (this page is generated by Security by default)

When the project is started, a string of login passwords will be output on the console

2.1 UserDetailsService interface

The user determines whether the user name exists

The Userdetails interface returned by UserDetailsService implements the following methods

View the implementation class of UserDetails to see the User class

The essence is to query the database through loadUserByUsername in userdetailsservice, and then return the User name, password and permission information in UserDetails

2.2 passwordEncoder interface

BCryptPasswordEncoder is the password parser officially recommended by Spring Security. It is usually used.

BCryptPasswordEncoder is a concrete implementation of bcrypt strong Hash method. It is a one-way encryption based on Hash algorithm. You can control the encryption strength through strength. The default value is 10

Password parser

You can see many implementation classes in its implementation classes, among which BCryptPasswordEncoder is officially recommended

test

@Test
void contextLoads() {
    BCryptPasswordEncoder pw = new BCryptPasswordEncoder();

    //encryption
    String encode = pw.encode("123");
    System.err.println(encode);

    //Compare passwords
    boolean matches = pw.matches("123", encode);
    System.err.println("==============");
    System.err.println(matches);
}

A random salt salt (equivalent to a random string) is added to make the encrypted ciphertext different each time

2.3 user defined login logic

When customizing the login logic, you need to use the UserDetailsService and PasswordEncoder explained earlier. However, Spring Security requires that there must be a PasswordEncoder instance in the container when performing custom login logic. Therefore, the new object cannot be directly.

Write SecurityConfig configuration class

/**
 * Security Configuration class
 */
@Configuration
public class SecurityConfig {

    @Bean
    public PasswordEncoder getPw(){
        return new BCryptPasswordEncoder();
    }
}

Writing implementation classes

UserServiceImpl.java

public class UserServiceImpl implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        //1. Query the database according to the user name. If there is no UsernameNotFoundException exception thrown
        if (!"admin".equals(username)){
            throw new UsernameNotFoundException("user name does not exist");
        }

        //2. Compare passwords (encrypted during registration). If the matching is successful, return UserDetails
        String password = passwordEncoder.encode("123");

        return new User(username,password,
                        AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal"));
    }
}

2.4 custom login page

  1. Login page login html
<!doctype html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>login interface</title>
    <style>
    html {
    background-color: #B5DEF2;
}

.wrapper {
    margin: 140px 0 140px auto;
    width: 884px;
}

.loginBox {
    background-color: #F0F4F6;
    /*Upper divcolor*/
    border: 1px solid #BfD6E1;
    border-radius: 5px;
    color: #444;
    font: 14px 'Microsoft YaHei', 'Microsoft YaHei ';
    margin: 0 auto;
    width: 388px
}

.loginBox .loginBoxCenter {
    border-bottom: 1px solid #DDE0E8;
    padding: 24px;
}

.loginBox .loginBoxCenter p {
    margin-bottom: 10px
}

.loginBox .loginBoxButtons {
    /*background-color: #F0F4F6;*/
    /*Lower divcolor*/
    border-top: 0px solid #FFF;
    border-bottom-left-radius: 5px;
    border-bottom-right-radius: 5px;
    line-height: 28px;
    overflow: hidden;
    padding: 20px 24px;
    vertical-align: center;
    filter: alpha(Opacity=80);
    -moz-opacity: 0.5;
    opacity: 0.5;
}

.loginBox .loginInput {
    border: 1px solid #D2D9dC;
    border-radius: 2px;
    color: #444;
    font: 12px 'Microsoft YaHei', 'Microsoft YaHei ';
    padding: 8px 14px;
    margin-bottom: 8px;
    width: 310px;
}

.loginBox .loginInput:FOCUS {
    border: 1px solid #B7D4EA;
    box-shadow: 0 0 8px #B7D4EA;
}

.loginBox .loginBtn {
    background-image: -moz-linear-gradient(to bottom, blue, #85CFEE);
    border: 1px solid #98CCE7;
    border-radius: 20px;
    box-shadow: inset rgba(255, 255, 255, 0.6) 0 1px 1px, rgba(0, 0, 0, 0.1) 0 1px 1px;
    color: #444;
    /*Sign in*/
    cursor: pointer;
    float: right;
    font: bold 13px Arial;
    padding: 10px 50px;
}

.loginBox .loginBtn:HOVER {
    background-image: -moz-linear-gradient(to top, blue, #85CFEE);
}

.loginBox a.forgetLink {
    color: #ABABAB;
    cursor: pointer;
    float: right;
    font: 11px/20px Arial;
    text-decoration: none;
    vertical-align: middle;
    /*Forget password*/
}

.loginBox a.forgetLink:HOVER {
    color: #000000;
    text-decoration: none;
    /*Forget password*/
}

.loginBox input#remember {
    vertical-align: middle;
}

.loginBox label[for="remember"] {
    font: 11px Arial;
}
</style>
    </head>
    <body>
    <div class="wrapper">
        <form action="/login" method="post">
        <div class="loginBox">
            <div class="loginBoxCenter">
                <p><label>user name:</label></p>
                <!--autofocus Specifies that the button should automatically get focus when the page is loaded. -->
                <!-- placeholder Provides a prompt that describes the expected value of the input field-->
                <p><input type="text" id="text" name="username" class="loginInput" autofocus="autofocus" required="required" autocomplete="off" placeholder="Please enter email address/cell-phone number" value="" /></p>
                    <!-- required Specify that the input fields must be filled in before submission-->
                    <p><label>password:</label></p>
                    <p><input type="password" id="password" name="password" class="loginInput" required="required" placeholder="Please input a password" value="" /></p>
                        <p><a class="forgetLink" href="#"> forgot your password</a></p>
                            <input id="remember" type="checkbox" name="remember" />
                            <label for="remember">Remember login status</label>
                                </div>
                                <div class="loginBoxButtons">


                                    <button class="loginBtn">Sign in</button>
                                        <div> New user registration</div>
                                        </div>
                                        </div>
                                        </form>
                                        </div>

                                        </body>
                                        </html>

Submit the form as action="/login"

And create a new main page main html

  1. Change permission information in SecurityConfig

/**
 * Security Configuration class
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public PasswordEncoder getPw(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //Set up form submission
        http.formLogin()
            //Custom landing page
            .loginPage("/login.html")
            //It must be the same as the interface action of form submission, and will execute custom login logic
            .loginProcessingUrl("/login")
            //Jump to the page Post after successful login
            .successForwardUrl("/toMain");

        //to grant authorization
        http.authorizeRequests()
            //Release / login HTML, no need to recognize
            .antMatchers("/login.html").permitAll()
            //All requests must be authenticated to access and must be logged in
            .anyRequest().authenticated();


        //Turn off csrf protection
        http.csrf().disable();
    }
}
  1. controller jump
@RequestMapping("/toMain")
public String main(){
    return "redirect:main.html";
}

2.5 custom error page

After login failure, there is no page Jump, and the url becomes

Simply write the login failure page error html

Add configuration class information

Add controller jump path

@RequestMapping("/toError")
public String error(){
    return "redirect:error.html";
}

Release the error page in the configuration class information

2.6 setting parameter name of request account and password

Username and password filter UsernamePasswordAuthenticationFilter class

test

  1. Change the page parameter to the value you want to pass

  2. Add configuration information to the configuration class to correspond the user name and password

    The default user name and password parameters are username and password

2.7 user defined login succeeded (processor)

If you want to log in successfully and jump to the page of station b, you will be prompted with an error

see. successForwardUrl("http://www.bilibili.com Successforwardurl method in '')

Custom implementation authentication jump AuthenticationSuccessHandler interface

  1. MyAuthenticationSuccessHandler.java
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    public MyAuthenticationSuccessHandler(String url) {
        this.url = url;
    }

    private String url;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.sendRedirect(url);
    }
}
  1. Configure the forwarding URL after successful login

The onAuthenticationSuccess method overridden in the custom MyAuthenticationSuccessHandler class can obtain the user's information in the authentication parameters

2.8 custom login failure (processor)

Similar to when login is successful

Create a custom interface MyAuthenticationFailureHandler

public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {    
    private String url;    
    public MyAuthenticationFailureHandler(String url) {        
        this.url = url;    
    }   
    
    @Override    
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {        
        response.sendRedirect(url);    
    }}

Modify configuration class

3. Authorization

Detailed explanation of anyRequest

In the previous authentication process, we have used anyRequest() to indicate that all requests match. Generally, this method will be used, and all settings need to be authenticated.

Detailed explanation of ant matchers

The method is defined as follows

public C antMatchers(String... antPatterns)

Parameters are non directional parameters, and each parameter is an ant expression used to match URL rules

  • ?: Match one character
  • *: matches 0 or more characters
  • **: matches 0 or more directories

In actual projects, all static resources are generally released, such as all script files under js files

.antMatchers("/js/**","/css/**").permitAll()

Or another matching method is as long as it is js is released at the end

.antMatchers("**/*.js").permitAll()

Detailed explanation of regexMatchers

Looking at the regexMatchers method, you can see that there are two implementation methods

The previous method in the two parameter method is an enumeration class that defines the request type

Specify the request method to access the resource

Detailed explanation of mvcMatchers

3.1 control of built-in permissions

3.2 role permission judgment

In addition to the built-in permission control explained earlier. Many other permission controls are also supported in Spring Security. These methods are generally used to judge whether the user has specific requirements after the user has been authenticated.

hasAuthority(String) permission

Judge whether the User has specific permission. The User's permission is specified when creating the User object in the User-defined login logic. In the following figure, admin and normal are User permissions. Admin and normal are strictly case sensitive.

  1. Configure permissions

    //Permission control, strictly case sensitive
    .antMatchers("/main1.html").hasAnyAuthority("admin")    
    //Set multiple permissions. If the user has the first permission in front, admin does not have it, the latter can also access it    
    .antMatchers("/main1.html").hasAnyAuthority("admin","amd")
    

    When defining the user at the beginning, the admin user is given admin permission. When the permission is changed to unauthorized permission, 403 is displayed

hasRole(String) role

Determine whether the user is the role

  1. Add user role in UserServiceImpl

    ROLE has been_ abc will be automatically set to this ROLE at the beginning

    //The abc1 role is required to access this path
    .antMatchers("/main1.html").hasRole("abc1")
    //Set up multiple roles. As long as one role wants to match, it can be accessed
    .antMatchers("/main1.html").hasAnyRole("abc","aaa")
    

hasIpAddress(String) IP address

Determine access rights based on IP

.antMatchers("/main1.html").hasIpAddress("127.0.0.2")

3.3 user defined 403 processing scheme

  1. Since 403 is due to insufficient permissions, customize MyAccessDeniedHandler and implement the AccessDeniedHandler interface

    @Componentpublic 
    class MyAccessDeniedHandler implements AccessDeniedHandler {    
        @Override    
        public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {        
            //Response status code        
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);        
            //Return JSON format        
            response.setHeader("Content-Type","application/json;charset=utf-8");        
            PrintWriter writer = response.getWriter();        
            writer.write("{\"state\":\"error\",\"msg\":\"Insufficient permissions\"}");        
            writer.flush();        
            writer.close();    
        }
    }
    

    Note that the @ Component annotation is added to allow spring to manage

  2. Configure in configuration class

    //First inject this class
    @Autowired
    MyAccessDeniedHandler myAccessDeniedHandler;
    
    //Configure the scheme http exceptionHandling()              
    .accessDeniedHandler(myAccessDeniedHandler);
    
  3. Test access

    When the permission is insufficient, a custom JSON string will appear

3.4 expression based access control

access() method use

In fact, the underlying implementation of the login user permission judgment learned before is to call access (expression)

You can use access() to achieve the same functions as the previous permission control

Similar to the previous writing

.antMatchers("/main1.html").access("hasRole('abc')")

3.4.1 user defined access() method

  1. Create MyService interface

    public interface MyService {    
        boolean hasPermission(HttpServletRequest request, Authentication authentication);
    }
    
  2. Create implementation class

    @Servicepublic class MyServiceImpl implements MyService {    @Override    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {        //Get principal data object obj = authentication getPrincipal();        // Instanceof is a reserved keyword for Java. Its function is to test whether the object on its left is an instance of the class on its right, and return the boolean data type// Judge whether the principal belongs to userdetails if (obj instanceof userdetails) {/ / obtain the corresponding permission userdetails userdetails = (userdetails) obj; collection <? Extensions grantedauthority > authorities = userdetails. Getauthorities(); / / judge whether the requested URI is in the permission return authorities.contains(new SimpleGrantedAuthority(request.getRequestURI()));        }        return false;    }}
    
  3. Configure permissions

  4. test

    This is because the URI obtained by the jump page after login jump is / main HTML, so you need to add configuration permission to the user in UserServiceImpl

3.4.2 annotation based access control

Some access control annotations are provided in Spring Security. These annotations are unavailable by default and need to be enabled through @ EnableGlobalMethodSecurity.

If the set conditions allow, the program will execute normally. If not allowed, 500 will be reported

These annotations can be written to the Service interface or method, or to the Controller or Controller's method. Usually, it is written on the Controller method to control whether the interface URL is allowed to be accessed.

@Secured

@Secured is specifically used to determine whether it has a ROLE. It can be written on a method or class, and the parameters are represented by ROLE_ start

  1. Open annotation

    Add @ EnableGlobalMethodSecurity(securedEnabled = true) to the startup class

  2. Add @ Secured annotation on controller method

    @RequestMapping("/toMain")
    @Secured("ROLE_abc")
    public String main(){
        return "redirect:main.html";
    }
    

@PreAuthorized / @ PostAuthorize

@PreAuthorized/@PostAuthorize are method or class level annotations

  • @PreAuthorize means that the access method or class judges the permission before execution. In most cases, this annotation is used. The values of the annotation parameters and the access() method parameters are the same, which are permission expressions
  • PostAuthorize means to judge the permission after the execution of a method or class. This annotation is rarely used
  1. Add EnableGlobalMethodSecurity(prePostEnabled = true) to the startup class

  2. Add annotation @ PreAuthorize on controller method

    @PreAuthorize("hasRole('abc')")
    public String main(){    
        return "redirect:main.html";
    }
    

3.5 memberme function realization

In Spring Security, Remember Me is the "Remember Me" function. Users only need to add the Remember Me check box when logging in, and the value is true. Spring Security will automatically store user information in the data source, so they can access it without logging in in in the future

Add dependency

When Spring Security implements the member me function, the underlying implementation relies on spring JDBC, so spring JDBC needs to be imported. In the future, we will use the mybatis framework rather than directly import spring JDBC. Therefore, when importing the mybatis initiator here, we also need to add a MySQL driver

 <!--mybatis-->
<dependency>    
    <groupId>org.mybatis.spring.boot</groupId>    
    <artifactId>mybatis-spring-boot-starter</artifactId>    
    <version>2.1.3</version>
</dependency>
<dependency>    
    <groupId>mysql</groupId>    
    <artifactId>mysql-connector-java</artifactId>    
    <version>8.0.25</version>
</dependency>

Write data source information

spring:	datasource:        
	driver-class-name: com.mysql.cj.jdbc.Driver       
	url: jdbc:mysql://localhost:3306/study_demo?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&serverTimezone=Asia/Shanghai        
	username: root        
	password: 123456

Add SecurityConfig configuration class

http. rememberMe(). Tokenrepository(), where an object is required in tokenrepository()

Check that this is an interface and click the implementation class

Select jdbc

Add in configuration class

Set remember me

The setting page remembers that the value of my name attribute is remember me

You can see that the bound database has automatically generated tables

After the table is generated, you need to turn off the automatic creation table in the configuration

4. Use in Thymeleaf

Spring Securitl can control the display effect in some view technologies. For example: JSP or thymeleaf. Thymeleaf is often used as the view presentation technology in projects that are not separated from the front and back and use Spring Boot.

Thymeleaf supports Spring Security in thymeleaf extras springsecurityx. The latest version is 5. Therefore, you need to add the dependency of this jar package and the dependency of thymeleaf in the project..

<!--thymeleaf springSecurity5-->
<dependency>    
    <groupId>org.thymeleaf.extras</groupId>    
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<dependency>    
    <groupId>org.springframework.boot</groupId>    
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

Introduce Thymeleaf namespace and security namespace in html page

<html lang="en" xmlns="http://www.w3.org/1999/xhtml"                
      xmlns:th="http://www.thymeleaf.org"                
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">

test

Create a template in the templates directory

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"      
      xmlns:th="http://www.thymeleaf.org"      
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
    <head>    
        <meta charset="UTF-8">    
        <title>Title</title>
    </head>
    <body>
        Login account: <span sec:authentication="name"></span> <br/>
        Login account: <span sec:authentication="principal.username"></span> <br/>
        voucher: <span sec:authentication="credentials"></span> <br/>
        Permissions and roles: <span sec:authentication="authorities"></span> <br/>
        Client address: <span sec:authentication="details.remoteAddress"></span> <br/>
        sessionId: <span sec:authentication="details.sessionId"></span> <br/>
    </body>
</html>

Add view control

@GetMapping("/demo")
public String demo(){    
    return "demo";
}

5. Log out

Users only need to send a / logout exit request to the spring security project

Add / logout hyperlink to the page

<a href="/logout">Log out</a>

For better results, exit configuration is usually added. The default exit url is / logout. After successful exit, jump to / login?logout

What happens after exiting and connecting? logout needs to be configured if you want to remove it

http.logout()    
    .logoutSuccessUrl("/login.html");

6. CSRF

This line of code has been stored in the configuration class before, http csrf. Disable() if the user cannot be authenticated without this line of code, this line of code means to turn off CSRF protection

6.1 what is CSRF

CSRF (Cross Site Request Forgery) cross site request forgery, also known as "OneClick Attack" or Session Riding. It is an illegal request to access a trusted site by forging a user's request.

Cross domain: as long as the network protocol, ip address and port are different, it is a cross domain request.

When the client interacts with the service, because the http protocol itself is a stateless protocol, cookies are introduced to record the client identity. The session id is stored in the cookie to identify the client. In the case of cross domain, the session id may be maliciously hijacked by a third party. When a request is sent to the server through this session id, the server will think that the request is legal, and many unexpected things may happen.

CSRF in Spring Security

Starting from Spring Security4, csrf protection is enabled by default. Requests are intercepted by default. Perform csrf processing. In order to ensure that other third-party websites are not accessed, csrf requires to carry the parameter name when accessing_ The csrf value is the content of the token (the token is generated at the server). If the token matches the token of the server successfully, the access is normal.

test

  1. Close csrf new login page template

  2. Add to form_ csrf

  3. Configure Controller

    @GetMapping("/showLogin")
    public String showLogin(){    
        return "login";
    }
    
  4. Release showConfig in SecurityConfig

After successful login, it can be seen that the Headers in the request carry_ csrf

7. Oauth2 certification

introduce

The third-party authentication technical scheme is mainly to solve the general standard problem of authentication protocol, because to realize cross system authentication, each system must follow a certain interface protocol.

OAUTH protocol provides a secure, open and simple standard for user resource authorization. At the same time, any third party can use OAUTH authentication service, and any service provider can implement its own OAUTH authentication service, so OAUTH is open. The industry provides various implementations of OAUTH, such as PHP, JavaScript, Java, Ruby and other language development packages, 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, which is enough to show that OAUTH standard has gradually become the standard of open resource authorization.

The Oauth protocol has now grown to 2.5% O version, 1 O version is too complex, 2 O version has been widely used.

Oauth2 protocol authentication process

role

client

It does not store resources. It needs to request the resources of the resource server through the authorization of the resource owner, such as Android client, Web client (browser), wechat client, etc.

Resource owner

It is usually a user or an application, that is, the owner of the resource.

Authorization server (also known as authentication server)

It is used to authenticate the identity owned by resources and authorize access to resources. To access resources, the client needs to be authorized by the resource owner through the authentication server.

Resource server

A server that stores resources. For example, the website user management server stores website user information, the website album server stores user album information, and the wechat resource service stores wechat user information. The client finally accesses the resource server to obtain resource information.

Common terms

  • Client credentials: the client LD and password of the client are used to authenticate the client.
  • Tokens: the access token issued by the authorization server after receiving the customer's request
  • Scopes: when a client requests an access token, the resource owner additionally specifies the subdivision permission 3.1.4 Token type

Token type

  • Authorization code: it is only used for authorization code and authorization type. It is used to exchange access token and refresh token
  • Access token: used to directly access protected resources on behalf of a user or service
  • Refresh token: used to authorize the server to obtain a refresh access token
  • BearerToken: whoever gets the Token can access resources, similar to cash
  • Proof of Possession(PoP) Token: you can verify whether the client has explicit ownership of the Token

advantage

More secure, the client does not contact the user password, and the server is easier to focus on protection, widely spread and continuously adopted

Short life and encapsulated token

Resource server and authorization server are decoupled, centralized authorization is implemented, and client is simplified

HTTP/JSON is friendly and easy to request and pass token s. Consider a variety of client architecture scenarios

Customers can have different levels of trust

shortcoming

  • The protocol framework is too broad, resulting in poor compatibility and interoperability of various implementations
  • It's not an authentication protocol. It doesn't tell you any user information by itself

Authorization mode

  • Authorization code mode

  • Simplified authorization mode

  • Password mode

  • Client mode

  • refresh token

8. SpringSecurity Oauth2

Authorization server

  • Authorize Endpoint: authorization breakpoint to authorize
  • Token Endpoint: the Token Endpoint that gets the corresponding token after authorization
  • Introduction endpoint: verify the endpoint and verify the legitimacy of the Token
  • Revocation Endpoint: revocates endpoint and revokes authorization

Spring Security Oauth2 architecture

8.1 Spring Security Oauth2 authorization code mode

  1. Create project import dependency

< version > 2.1.3 is used for the springboot version here RELEASE</version>

If the version does not correspond, the project will not run

<properties>
    <java.version>1.8</java.version>
    <spring-cloud.version>Greenwich.SR2</spring-cloud.version>
</properties>
<dependencies>
   <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
   </dependency>

   <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
   </dependency>

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

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

   <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
   </dependency>
  </dependencies>

 <dependencyManagement>
   <dependencies>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>${spring-cloud.version}</version>
    <type>pom</type>
    <scope>import</scope>
    </dependency>
   </dependencies>
  </dependencyManagement>

<build>
   <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
   </plugins>
</build>

Create a User pojo entity class to implement the UserDetails interface

public class User implements UserDetails {

    private String username;
    private String password;
    private List<GrantedAuthority> authorities;

    public User(String username, String password, List<GrantedAuthority> authorities) {
        this.username = username;
        this.password = password;
        this.authorities = authorities;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

Create UserService to implement UserDetailsService interface

@Service
public class UserService implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        String password = passwordEncoder.encode("123456");
        return new User(username,password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));

    }
}

Configure SecurityConfig information

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                //Release the following path
                .antMatchers("/oauth/**","/login/**","/logout/**")
                .permitAll()
                //The remaining requests need to be authenticated
                .anyRequest()
                .authenticated()
                .and()
                //All form requests are released
                .formLogin()
                .permitAll()
                .and()
                //Turn off csrf
                .csrf().disable();

    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

For the convenience of demonstration, the resource server and authorization server are configured together

Configure authorization server

public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;
    
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //For the convenience of demonstration, it is put in memory first, which is not the case in actual development
        clients.inMemory()
                //client ID 
                .withClient("client")
                //secret key
                .secret(passwordEncoder.encode("112233"))
                //Redirect address
                .redirectUris("http://www.baidu.com")
                //Scope of authorization
                .scopes("all")
                /**
                 * Authorization type
                 * authorization_code Authorization code mode
                 */
                .authorizedGrantTypes("authorization_code");
    }
}

Configure resource server

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .requestMatchers()
                .antMatchers("/user/**");
    }
}

Configure Controller

@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/getCurrentUser")
    public Object getCurrentUser(Authentication authentication){
        return authentication.getPrincipal();
    }
}

test

  • Obtain authorization code

http://localhost:8080/oauth/authorize?response_type=code&client_id=admin&redirect_uri=http://www.baidu.com&scope=all

Since the defined client ID is client, the client_ Change id = admin to client

Log in and jump to the server

Select allow to jump to the specified page

Get token code=t0k7WA

Obtain the token according to the authorization code (Post request)

  1. Get token

Information to be added in the body

  • grant_type: authorization type, fill in authorization_code, indicating authorization code mode
  • Code: authorization code is the authorization code just obtained. Note: the authorization code is invalid after being used only once and needs to be applied again.
  • client_id: client ID
  • redirect_uri: the jump url when applying for authorization code, which must be the same as the redirect used when applying for authorization code_ Uri consistent.
  • Scope: authorization scope.

If authentication fails, the server will return 401

  1. Use token to get the current user information

8.2 password mode

  • Configure the authentication manager in SecurityConfig

    @Bean
    public AuthenticationManager authenticationManager() throws 		Exception {
        return super.authenticationManager();
    }
    
  • Override the configuration in authorization service configuration AuthorizationServerConfig

    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Autowired
        private AuthenticationManager authenticationManager;
    
        @Autowired
        private UserService userService;
    
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            //For the convenience of demonstration, it is put in memory first, which is not the case in actual development
            clients.inMemory()
                    //client ID 
                    .withClient("client")
                    //secret key
                    .secret(passwordEncoder.encode("112233"))
                    //Redirect address
                    .redirectUris("http://www.baidu.com")
                    //Scope of authorization
                    .scopes("all")
                    /**
                     * Authorization type (multiple modes can be used simultaneously)
                     * authorization_code Authorization code mode
                     * password Password mode
                     */
                    .authorizedGrantTypes("authorization_code","password");
        }
    
    	/**
         * Password mode
         * @param endpoints
         * @throws Exception
         */
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
                endpoints.authenticationManager(authenticationManager)
                        .userDetailsService(userService);
        }
    }
    

    test

    Send new token

    Add the token to the request to get

8.3 storing token s in Redis

Previously, the token was stored directly in memory, which is unreasonable in the production environment. Next, it is transformed to be stored in Redis

1. Add dependency and configuration

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>2.1.9.RELEASE</version>
</dependency>

<!--commons-pool2 Object pool dependency-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

Configure redis configuration information in Application

# Redis
spring:
  redis:
    host: 127.0.0.1

2. Create RedisConfig

@Configuration
public class RedisConfig {

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Bean
    public TokenStore redisTokenStore(){
        return new RedisTokenStore(redisConnectionFactory);
    }
}

3. Configure in the password mode in AuthorizationServerConfig

@Autowired
@Qualifier("redisTokenStore")
private TokenStore tokenStore;

/**
 * Password mode
 * @param endpoints
 * @throws Exception
 */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints.authenticationManager(authenticationManager)
                    .userDetailsService(userService)
                    .tokenStore(tokenStore);
    }

4. Test and send the request in password mode in postman

You can see it in Redis

9. JWT

9.1 common authentication mechanisms

HTTP Basic Auth

The simple explanation of HTTP Basic Auth is to provide the user's username and password every time the API is requested. In short, Basic Auth is the simplest authentication method used with the RESTful API. It only needs to provide the username and password. However, due to the risk of exposing the username and password to the third-party client, it is less and less used in the production environment. Therefore, when developing open restful APIs, try to avoid using HTTP Basic Auth.

Cookie Auth

Cookie authentication mechanism is to create a session object on the server side and a cookie object on the browser side of the client side for one request authentication; The state management is realized by matching the cookie object brought by the client with the session object on the server. By default, cookies are deleted when we close the browser. However, you can modify the expiration time of the cookie to make the cookie valid for a certain period of time.

OAuth

OAuth (Open Authorization) is an open authorization standard that allows a user to allow a third-party application to access the user's private resources (such as photos, videos and contact lists) stored on a web service without providing the user name and password to the third-party application. For example, the website logs in through wechat and microblog, which is mainly used for third-party login.

OAuth allows users to provide a token instead of a user name and password to access their data stored in a specific service provider. Each token authorizes a specific third-party system (e.g., video editing website) to access specific resources (e.g., only videos in an album) within a specific time period (e.g., the next 2 hours). In this way, OAuth allows users to authorize third-party websites to access some specific information they store in another service provider, rather than all content.

Token Auth

Using the Token based authentication method, the server does not need to store the user's login record. The general process is as follows:

  1. The client requests login with user name and password
  2. The server receives a request to verify the user name and password
  3. After the verification is successful, the server will issue a Token and then send the Token to the client
  4. After receiving the Token, the client can store it, for example, in a Cookie
  5. Each time the client requests resources from the server, it needs to bring the Token issued by the server
  6. The server receives the request, and then verifies the Token carried in the client request. If the verification is successful, it returns the requested data to the client

It is safer than the first method, saves server resources more than the second method, and is lighter than the third method

Specifically: the advantages of Token Auth (what are the advantages of Token mechanism over Cookie mechanism?)

  1. Support cross domain access: cookies do not allow domain access, which does not exist for the Token mechanism. The premise is that the transmitted user authentication information is transmitted through the HTTP header! I
  2. Stateless (also known as · server extensible line): the Token mechanism does not need to store session information on the server, because the Token itself contains all logins
    The user's information only needs to store the status information in the client's cookie or local media
  3. More applicable to CON: you can request all the data of your server (such as javascript, HTML, pictures, etc.) through the content distribution network, and your service
    Just provide API at the end
  4. Decoupling: there is no need to bind to a specific authentication scheme. Token s can be generated anywhere, as long as you can call them when your API is called
  5. More suitable for mobile applications: when your client is a native platform (iOS, Android, Windows 10, etc.), cookies are not supported (you need to process them through the Cookie container). At this time, it will be much easier to adopt the Token authentication mechanism.
  6. CSRF: since you no longer rely on cookies, you don't need to consider the prevention of CSRF (Cross Site Request Forgery).
  7. Performance the round-trip time of a network (query session information through the database) is much more time-consuming than the Token verification and parsing of an HMACSHA256j calculation
  8. No special treatment is required for the login page. If you use Protractor for function test, no special treatment is required for the login page
  9. Based on standardization, your API can adopt the standardized JSON Web Token(JWT), which has been supported by multiple back-end libraries (. NET, Ruby, Java, Python, PHP) and companies

9.2 introduction to JWT

What is JWT

JSON Web Token (JWT) is an open industry standard (RFC 7519). It defines a brief and self-contained protocol format for transmitting json objects between communication parties. The transmitted information can be verified and trusted by digital signature. JWT can use HMAC algorithm or RSA public / private key pair to sign to prevent tampering

Advantages of JWT token:

1. jwt be based on json,Very easy to parse.
2. Rich content can be customized in the token, which is easy to expand.
3. Through asymmetric encryption algorithm and digital signature technology, JWT Tamper proof, high security.
4. Resource service usage JWT Authorization can be completed without relying on authentication services.

Disadvantages:

​ 1. The JWT token is long and occupies a large storage space.

JWT composition

A JWT is actually a string. It consists of three parts: header, payload and signature

1. Header:

The header is used to describe the most basic information about the JWT, such as its type (i.e. JWT) and the algorithm used for signature (such as HMAC SHA256 or RSA). This can also be represented as a JSON object.

{
    "alg":"HS256",
    "typ":"JWT"
}
  • typ: is the type
  • alg: signature algorithm. The algorithm used here is HS256 algorithm

We BASE64 encode the json string in the header

Base64 is a representation of binary data based on 64 printable characters. Since the 6th power of 2 is equal to 64, every 6 bits are a unit corresponding to a printable character. Three bytes have 24 bits, corresponding to four Base64 units, that is, three bytes need to be represented by four printable characters. JDK provides very convenient BASE64Encoder and BASE64Decoder, which can easily complete Base64 based encoding and decoding.

2. Load (Payload)

The second part is the load, which is the place to store effective information. The name seems to refer specifically to the goods carried on the aircraft. These valid information includes three parts:

  • Declaration registered in the standard (recommended but not mandatory)
iss: jwt Issuer
sub: jwt Target users
aud: receive jwt Party of
exp: jwt Expiration time of, The expiration time must be greater than the issuing time
nbf: Define before what time, Should jwt Are not available
iat: jwt Date of issue
jti: jwt Unique identity of, Mainly used as a disposable token,To avoid replay attacks
  • Public statement

    Any information can be added to the public statement. Generally, the user's relevant information or other necessary information required by the business can be added However, it is not recommended to add sensitive information because this part can be decrypted on the client

  • Private declaration

    Private declaration is a declaration jointly defined by providers and consumers. It is generally not recommended to store sensitive information, because base64 is symmetrically decrypted, which means that this part of information can be classified as plaintext information.

    This refers to the custom claim. For example, the name in the following example belongs to the custom claim. The difference between these claims and the claims specified in the JWT standard lies in: the claims specified in the JWT. After receiving the JWT, the receiver of the JWT knows how to verify the claims of these standards (whether they can be verified or not); private claims will not be verified unless the receiver is explicitly told to verify these claims and rules.

{
    "sub":"123456",
    "name":"ahui",
    "iat":"12133333",
}

Where sub is a standard declaration and name is a custom declaration (public or private)

Then it is base64 encoded to get the second part of jwt

Don't put some sensitive information in the statement

3. Visa and signature

The third part of jwt is a visa information, which consists of three parts:

  • Header (after Base64)

  • Payload (after Base64)

  • Secret (salt, be sure to keep it secret)

    This part requires base64 encrypted header and base64 encrypted payload The string formed by the connection is then encrypted by salt secret combination through the encryption method declared in the header, and then the third part of jwt is formed:

Use these three parts Join into a complete string to form the final jwt

Note: secret is saved on the server side, and jwt issuance is also generated on the server side. Secret is used for jwt issuance and jwt verification. Therefore, it is the private key of your server side and should not be revealed in any scenario. Once the client knows this secret, it means that the client can sign jwt it

9.3 JJWT introduction

JJWT is a Java library that provides end-to-end JWT creation and validation. Always free and open source (Apache License, version 2.0), JJWT is easy to use and understand. It is designed as a smooth interface centered on architecture, hiding most of its complexity.

Create project tests and use JJWT to generate JWT

1. Create a new boot project for testing

Import pom dependencies

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--jwt rely on-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
</dependencies>

Create test class generation jwt

/**
     * Generate jwt
     */
    @Test
    public void testJwt() {
        JwtBuilder jwtBuilder = Jwts.builder()
                //Unique ID{"id":"314"}
                .setId("314")
                //Received user {"sub":"Ahui"}
                .setSubject("Ahui")
                //Issued on {"iat": "....}
                .setIssuedAt(new Date())
                //Signature algorithm, and key
                .signWith(SignatureAlgorithm.HS256, "xxxx");
        //Issue token
        String token = jwtBuilder.compact();
        System.err.println(token);
    }

Decrypted in jwt

Or use separated base64 to de encode and decrypt

Verification and parsing of token

We have just created a token. In the web application, this operation is performed by the server and sent to the client. The client needs to carry this token when sending a request to the server next time (it's like holding a ticket). The server should parse the information in the token (such as user id) when receiving this token, Query the database according to this information and return the corresponding results.

/**
 * Parse token
 */
    @Test
    public void testParseToken(){
        String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIzMTQiLCJzdWIiOiJBaHVpIiwiaWF0IjoxNjI1NDY4ODM0fQ.aH0QUMEQwyeZc6Ab3cBzckF5t3TC-3KnvRjPJu_XtiI";
        Claims claims = (Claims) Jwts.parser()
                //The key must be consistent with that set at the time of issuance
                .setSigningKey("xxxx")
                .parse(token)
                .getBody();
        System.err.println("id=" + claims.getId());
        System.err.println("sub=" + claims.getSubject());
        System.err.println("iat=" + claims.getIssuedAt());
    }
}

Set token expiration time

In many cases, we do not want the issued token to be permanent (the token in the previous section is permanent), so we can add an expiration time for the token. Reason: the server does not record the token sent from the server, so there is a disadvantage that the server cannot actively control the immediate invalidation of a token.

Test:

/**
     * Build jwt (with expiration time and start time)
     */
    @Test
    public void testJwtHasExpire() {
        //current time 
        long date = System.currentTimeMillis();
        //Failure time
        long exp = date + 60 * 1000;
        JwtBuilder jwtBuilder = Jwts.builder()
                //Unique ID{"id":"314"}
                .setId("314")
                //Received user {"sub":"Ahui"}
                .setSubject("Ahui")
                //Issued on {"iat": "....}
                .setIssuedAt(new Date())
                //Signature algorithm, and key
                .signWith(SignatureAlgorithm.HS256, "xxxx")
                //Set expiration time
                .setExpiration(new Date(exp));
        //Issue token
        String token = jwtBuilder.compact();
        System.err.println(token);


        System.err.println("=================");
        String[] split = token.split("\\.");
        System.err.println(Base64Codec.BASE64.decodeToString(split[0]));
        System.err.println(Base64Codec.BASE64.decodeToString(split[1]));
        //The third part will be garbled
        System.err.println(Base64Codec.BASE64.decodeToString(split[2]));
    }

This is equivalent to adding the expiration time configuration

//current time 
long date = System.currentTimeMillis();
//Failure time
long exp = date + 60 * 1000;

//Set expiration time
.setExpiration(new Date(exp));

It can be parsed within one minute, and exceptions will be thrown after one minute

Custom claims

Add a custom claim to the configuration

Get after decoding

10. Spring securityoauth2 integrates JWT

Use the previous redis integration project for testing

Because jwt is stateless, redis is not required, so delete the dependency and configuration test first

1. Add jwt configuration information

JwtTokenStoreConfig.java

@Configuration
public class JwtTokenStoreConfig {

    @Bean
    public TokenStore jwtTokenStore(){
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter(){
        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        //Set jwt key
        jwtAccessTokenConverter.setSigningKey("test_key");
        return jwtAccessTokenConverter;
    }
}

2. Add configuration information in authorizationserverconfig

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserService userService;

    @Autowired
    @Qualifier("jwtTokenStore")
    private TokenStore tokenStore;

    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;


    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //For the convenience of demonstration, it is put in memory first, which is not the case in actual development
        clients.inMemory()
                //client ID 
                .withClient("client")
                //secret key
                .secret(passwordEncoder.encode("112233"))
                //Redirect address
                .redirectUris("http://www.baidu.com")
                //Scope of authorization
                .scopes("all")
                /**
                 * Authorization type (multiple modes can be used simultaneously)
                 * authorization_code Authorization code mode
                 * password Password mode
                 */
                .authorizedGrantTypes("authorization_code","password");
    }

    /**
     * Password mode
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints.authenticationManager(authenticationManager)
                    .userDetailsService(userService)
                    //Convert accessToken to JwtToken
                    .tokenStore(tokenStore)
                    .accessTokenConverter(jwtAccessTokenConverter);
    }
}

3. Test with Postman

Still use password mode

It can be decoded on the Jwt official website

10.1 expand JWT storage content

Create a new JwtTokenEnhancer extended memory class

JwtTokenEnhancer.java

/**
 * Expand JWT storage content
 */
public class JwtTokenEnhancer implements TokenEnhancer {
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {

        Map<String, Object> map = new HashMap<>();
        map.put("enhance","enhance info");
        //Force oAuth2AccessToken to DefaultOAuth2AccessToken type
        ((DefaultOAuth2AccessToken)oAuth2AccessToken).setAdditionalInformation(map);

        return oAuth2AccessToken;
    }
}

You can see that the parameter OAuth2AccessToken is an interface, and the DefaultOAuth2AccessToken in its implementation class contains a setAdditionalInformation method

The required parameter is a Map

Configure in JwtTokenStoreConfig

@Bean
public JwtTokenEnhancer jwtTokenEnhancer(){
    return new JwtTokenEnhancer();
}

**Configure tokenEnhancer in AuthorizationServerConfig * *

Add tokenEnhancer and check this method. You can know that this is an interface to find its implementation class

So you need to prepare the implementation class

Inject JwtTokenEnhancer

@Autowired
private JwtTokenEnhancer jwtTokenEnhancer;

Final code

@Autowired
private JwtTokenEnhancer jwtTokenEnhancer;

	/**
     * Password mode
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        //Set Jwt enhancements
        TokenEnhancerChain chain = new TokenEnhancerChain();
        List<TokenEnhancer> delegates = new ArrayList<>();
        delegates.add(jwtTokenEnhancer);
        delegates.add(jwtAccessTokenConverter);
        chain.setTokenEnhancers(delegates);

        endpoints.authenticationManager(authenticationManager)
                    .userDetailsService(userService)
                    //Convert accessToken to JwtToken
                    .tokenStore(tokenStore)
                    .accessTokenConverter(jwtAccessTokenConverter)
                    //Set Jwt enhancements
                    .tokenEnhancer(chain);
    }
}

test

Use postman to send the obtained token to the official website for decoding and viewing

10.2 parsing Jwt content

1. Add jwt dependency

<!--jwt-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

2. Modify UserController

@RequestMapping("/getCurrentUser")
    public Object getCurrentUser(Authentication authentication, HttpServletRequest httpServletRequest){
		 //Get Authorization in request header
        String header = httpServletRequest.getHeader("Authorization");
        //Intercept the token in Authorization
        String token = header.substring(header.lastIndexOf("bearer") + 7);
        return Jwts.parser()
                .setSigningKey("test_key".getBytes(StandardCharsets.UTF_8))
                .parseClaimsJws(token)
                .getBody();
    }

3. Use postman for testing

  • First, use the previous password mode to obtain the token

  • Parsing by jwt token

10.3 refresh token

Configure in AuthorizationServerConfig

Configure expiration time and refresh Token Mode

test

Using postman to get the token, you can see that there is an additional refresh token

To test, open a test refresh token

Fill in the Authorization and body tests to get a new token

It can then be accessed with the new token

11. Single sign on

What is Single Sign On? The full name of Single Sign On is Single Sign On (hereinafter referred to as SSO), which means that if you log in to one system in a multi system application group, you can be authorized in all other systems without logging in again, including Single Sign On and single sign off

11.1 integrated SSO based on springSecurityOauth2

Use the above items as the authentication server (as the authentication system in the single sign on system)

Create the corresponding springboot client for testing

  1. Import dependency on client
<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.onlylmf</groupId>
    <artifactId>oauth2-client01-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>oauth2-client01-demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR2</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.3.1.RELEASE</version>
        </dependency>

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

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

        <!--jwt-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--commons-pool2 Object pool dependency-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
  1. to configure

Write configuration file

server:
  port: 8081
  #Prevent Cookie conflicts, which will cause login authentication to fail
  servlet:
    session:
      cookie:
        name: OAUTH2-CLIENT-SESSIONID01


#Authorization server address
oauth2-server-url: http://localhost:8080

#Configuration corresponding to the authorization server
security:
  oauth2:
    client:
      client-id: client
      client-secret: 112233  #The key configured by your own server
      user-authorization-uri: ${oauth2-server-url}/oauth/authorize #Obtain authorization code
      access-token-uri: ${oauth2-server-url}/oauth/token #Get accessToken
    resource:
      jwt:
        key-uri: ${oauth2-server-url}/oauth/token_key #Get jwt token

Login interface

Enable sso single sign on

  1. Configure on the server side

Modify the redirection address to the client login interface

Configure authentication information

  1. Test access
  • visit

    Jump to by default

    Login invokes the login logic of UserDetailsService defined by the server before

    Because automatic authorization is set on the server autoApprove(true)

Topics: Java Spring security