Spring Security User Authentication

Table of contents

1. User authentication

1. Demand analysis

2. Connect to the user center database

1. Connect to database authentication

2. Expand user identity information

How to extend Spring Security's user identity information?

3. Resource service obtains user identity


1. User authentication

1.  Demand analysis

So far we have understood the process of using Spring Security for authentication and authorization. This section implements the user authentication function.

At present, major websites have many authentication methods: account password authentication, mobile phone verification code authentication, scan code login, etc.

If you don’t understand, you can read it here: OAuth2 authentication process_Relievedz’s blog-CSDN blog 

2. Connect to the user center database

1.  Connect to database authentication

The based authentication process has been tested and passed during the research of Spring Security. So far, the user authentication process is as follows:

 

The user information required for authentication is stored in the user center database. Now it is necessary to connect the authentication service to the database to query user information.

In the process of studying Spring Security, user information is hard-coded, as follows:

//Configure user information service 
@Bean 
public UserDetailsService userDetailsService() { 
    //Configure user information here, temporarily use this method to store users in memory 
    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; 
}

We need to connect to the user center database in the authentication service to query user information.

How to use Spring Security to connect to database authentication?

When learning the working principle of Spring Security, there was an execution flow chart, as shown below:

The user submits the account and password and DaoAuthenticationProvider calls the loadUserByUsername() method of UserDetailsService to obtain the UserDetails user information.

Query the source code of DaoAuthenticationProvider as follows:

UserDetailsService is an interface, as follows: This code is the source code of the framework and does not need to be written by yourself.

package org.springframework.security.core.userdetails;

public interface UserDetailsService {
    UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}

 UserDetails is the user information interface:  this code is the source code of the framework and does not need to be written by yourself.

public interface UserDetails extends Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();

    String getPassword();

    String getUsername();

    boolean isAccountNonExpired();

    boolean isAccountNonLocked();

    boolean isCredentialsNonExpired();

    boolean isEnabled();
}

 

We only need to implement the UserDetailsService interface to query the database to obtain user information and return user information of the UserDetails type.

First shield the originally defined UserDetailsService. : This is the code I wrote myself

    //Configure user information service 
// @Bean 
// public UserDetailsService userDetailsService() { 
// //Configure user information here, temporarily use this method to store users in memory 
// 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; 
// }

Customize the UserDetailsService below   to write it yourself

package com.xuecheng.ucenter.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.xuecheng.ucenter.mapper.XcUserMapper;
import com.xuecheng.ucenter.model.po.XcUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component; 

/**
 * @program: xuecheng-plus-project148
 * @description: The interface queries the database to obtain user information and returns user information of type UserDetails. 
 * @author: Mr.Zhang 
 * @create: 2023-03-17 08:52 
 **/ 
@Slf4j 
@Component 
public class UserServiceImpl implements UserDetailsService { 

    @Autowired 
    XcUserMapper userMapper; 

    /** 
     * Query user information based on account number 
     * @param s Account 
     * @return 
     * @throws UsernameNotFoundException 
     */ 
    @Override 
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { 
        //Account 
        String username = s; 
        //Query the database based on username account 
        XcUser xcUser = userMapper.selectOne(new LambdaQueryWrapper<XcUser>().eq(XcUser:: getUsername, username));

            //If the user does not exist, just return null. The spring 
        security framework throws an exception 
        that the user 
        does not exist 
        . A UserDetails object is returned to the spring security framework, and the framework performs password comparison 
        String password = xcUser.getPassword(); 
        // //User authority, if not reported Cannot pass a null GrantedAuthority collection 
        String[] authorities= {"test"} ; 
        //Create a UserDetails object, and add UserDetails to UserDetails when the authorization function is implemented. 
        userDetails = User.withUsername(username).password(password).authorities(authorities).build(); 
        return userDetails; 
    } 
}

At this point, we need to be clear about how the framework executes after calling the loadUserByUsername() method to get the user information, as shown in the figure below:

 

The password in the database is encrypted, and the password entered by the user is in plain text. We need to modify the password formatter PasswordEncoder. The original NoOpPasswordEncoder was used, which compares passwords in plain text. Now we change it to BCryptPasswordEncoder, which compares the password entered by the user. The password is encoded in BCrypt format and compared with the password in the database.

as follows:

    @Bean 
    public PasswordEncoder passwordEncoder() { 
// //The password is in clear text 
// return NoOpPasswordEncoder.getInstance(); 
        return new BCryptPasswordEncoder(); 
    }

We test BCryptPasswordEncoder through the test code, as follows:

//Perform password comparison
public static void main(String[] args) { 
    String password = "111111"; 
    BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); 

    for (int i = 0; i < 5; i++) { 
        // generate password 
        String encode = passwordEncoder.encode (password); 
        System.out.println(encode); 
        //Check the password, parameter 1 is the input plain text, parameter 2 is the encrypted string of the correct password boolean 
        matches = passwordEncoder.matches(password, encode); 
        System.out .println(matches); 
    } 
    boolean matches = passwordEncoder.matches("1111", "$2a$10$Q0ItVMXc/VwrlRE7NGBFtetut6o7vxpSjQDcKvtakIrCnxOWf.LV."); 
    System.out.println(matches); 
}

Modify the password in the database to Bcrypt format and record the plain text password, which will be needed when applying for a token later.

Since changing the password encoding method also requires changing the client's key to Bcrypt format.

//Client Details Service 
    @Override 
    public void configure(ClientDetailsServiceConfigurer clients) 
            throws Exception { 
        clients.inMemory()// Use in-memory storage.withClient 
                ("XcWebApp")// client_id 
// .secret("XcWebApp")/ /Client key.secret 
                (new BCryptPasswordEncoder().encode("XcWebApp"))//Client key.resourceIds 
                ("xuecheng-plus")//Resource list.authorizedGrantTypes 
                ("authorization_code", "password", "client_credentials", "implicit", "refresh_token")// authorization_code,password,refresh_token,implicit,client_credentials allowed by the client 
                .scopes("all")// Allowed authorization 
                scope.autoApprove(false)//false Jump to the authorization page
                //The redirect address for the client to receive the authorization 
                code.redirectUris("http://www.xuecheng-plus.com") 
        ; 
    }

Now restart the authentication service.

Use httpclient to test below:

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

Parameter introduction:

### Password mode 
POST { 
  
  {server port number}}/auth/oauth/token? path client_id=XcWebApp&client_secret=XcWebApp&grant_type=password&username=t1(my database user name)&password=111111(database password)

Enter the correct account number and password, and the token application is successful.

If you enter a wrong password, an error will be reported:

{   "error": "invalid_grant",   "error_description": "Wrong username or password" }


Entering the wrong account number, an error is reported:

{
  "error": "unauthorized",
  "error_description": "UserDetailsService returned null, which is an interface contract violation"
}

 Entered correctly:

2. Expand user identity information

The user table stores the user's account number, mobile phone number, email, nickname, qq and other information, and the UserDetails interface only returns username, password and other information, as follows:

public interface UserDetails extends Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();

    String getPassword();

    String getUsername();

    boolean isAccountNonExpired();

    boolean isAccountNonLocked();

    boolean isCredentialsNonExpired();

    boolean isEnabled();
}

We need to expand the user identity information and store the user's nickname, avatar, QQ and other information in the jwt token.

How to extend Spring Security 's user identity information?

During the authentication phase, DaoAuthenticationProvider will call UserDetailService to query the user's information, where complete user information can be obtained. Since the user identity information in the JWT token comes from UserDetails, only username is defined as the user’s identity information in UserDetails. There are two ideas here: the first is to extend UserDetails to include more custom attributes, and the second is also The content of username can be extended, such as storing json data content as the content of username. In comparison, option two is relatively simple and does not require destroying the structure of UserDetails. We adopt option two.

Modify UserServiceImpl as follows:

package com.xuecheng.ucenter.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.xuecheng.ucenter.mapper.XcUserMapper;
import com.xuecheng.ucenter.model.po.XcUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component; 
        String username = s;

/**
 * @program: xuecheng-plus-project148 
 * @description: The interface queries the database to obtain user information and returns user information of the UserDetails type. 
 * @author: Mr.Zhang 
 * @create: 2023-03-17 08:52 
 **/ 
@Slf4j 
@Component 
public class UserServiceImpl implements UserDetailsService { 

    @Autowired 
    XcUserMapper userMapper; 

    /** 
     * Query user information according to account number 
     * @param s Account 
     * @return 
     * @throws UsernameNotFoundException 
     */ 
    @Override 
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { 
        //Account 
        //Query the database according to the username account 
        XcUser xcUser = userMapper.selectOne(new LambdaQueryWrapper<XcUser>().eq(XcUser::getUsername, username)); 

        //If the user does not exist, just return null, spring security framework An exception is thrown if the user does not exist 
        if (xcUser==null){ 
            return null; 
        } 
        //If the user is found to get the correct password, it will be encapsulated into a UserDetails object and returned to the spring security framework, and the framework will compare the password 
        String password = xcUser.getPassword(); 
        // //User authority, if not reported Cannot pass a null GrantedAuthority collection 
        String[] authorities= {"test"}; 
        xcUser.setPassword(null); 
        //Create UserDetails object, authority information to be When implementing the authorization function, add 
        //Convert user information to json in UserDetail 
        String userJson = JSON.toJSONString(xcUser); 
        UserDetails userDetails = User.withUsername(userJson).password(password).authorities(authorities).build();

        return userDetails;
    }
}

Restart the authentication service and regenerate the token. The generation is successful.

We can use check_token to query the content of jwt

###Verify jwt token 
POST { 
   
   {auth_host}}/auth/oauth/check_token?token=

An example response is as follows,

{
  "aud": [
    "xuecheng-plus"
  ],
  "user_name": "{\"createTime\":\"2022-09-28T08:32:03\",\"id\":\"51\",\"name\":\"学生2\",\"sex\":\"1\",\"status\":\"1\",\"username\":\"stu2\",\"utype\":\"101001\"}",
  "scope": [
    "all"
  ],
  "active": true,
  "exp": 1679025018,
  "authorities": [
    "test"
  ],
  "jti": "14c4085b-7787-4ce8-8c24-90f1fc80df34",
  "client_id": "XcWebApp"
}

user_name stores the json format of user information. In the resource service, the content of the json format can be taken out and converted into a user object for use.

3. Resource service obtains user identity

Write a tool class below to use in each microservice to obtain the object of the currently logged in user.

package com.xuecheng.content.util; 

import com.alibaba.fastjson.JSON; 
import lombok.Data; 
import lombok.extern.slf4j.Slf4j; 
import org.springframework.security.core.context.SecurityContextHolder; 

import java.io. Serializable; 
import java.time.LocalDateTime; 


/** 
 * @program: xuecheng-plus-project148 
 * @description: Get the current user identity tool class 
 * @author: Mr.Zhang 
 * @create: 2023-03-17 10:05 
 **/ 
@Slf4j 
public class SecurityUtil { 

    public static XcUser getUser() { 
        try { 
                //Get user identity information
            Object principalObj = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
            if (principalObj instanceof String) { 
                String principal = principalObj.toString(); 
                //Convert json into object 
                XcUser user = JSON.parseObject(principal, XcUser.class); 
                return user; 
            } 
        } catch (Exception e) { 
            log. error("Error getting the identity of the currently logged in user: {}", e.getMessage()); 
            e.printStackTrace(); 
        } 

        return null; 
    } 


    @Data 
    public static class XcUser implements Serializable { 

        private static final long serialVersionUID = 1L; 

        private String id; 

        private String username;
 
        private String password; 

        private String salt;

        private String name;
        private String nickname;
        private String wxUnionid;
        private String companyId;
        /**
         * 头像
         */
        private String userpic;

        private String utype;

        private LocalDateTime birthday;

        private String sex;

        private String email;

        private String cellphone;

        private String qq;

        /**
         * 用户状态
         */
        private String status;

        private LocalDateTime createTime;

        private LocalDateTime updateTime;


    }


}

Next, test this tool class in the content management service, taking the course information query interface as an example:

  @ApiOperation("Query courses according to id") 
    @GetMapping("/course/{courseId}") 
    public CourseBaseInfoDto getCourseBaseById(@PathVariable Long courseId){ 
        //Get the identity of the current user 
// Object principal = SecurityContextHolder.getContext(). getAuthentication().getPrincipal(); 
// System.out.println("Authentication"+principal); 
        SecurityUtil.XcUser user = SecurityUtil.getUser(); 
        System.out.println(user.getUsername()); 
        CourseBaseInfoDto courseBaseInfo = courseBaseInfoService.getCourseBaseInfo(courseId); 
        return courseBaseInfo; 
    }

Restart the content management service:

1. Start the authentication service, gateway, and content management service

2. Generate a new token

3. Bring the token to access the query course interface of the content management service

 

Guess you like

Origin blog.csdn.net/Relievedz/article/details/129611666