Certification service-----technical points and highlights

big technology

Nacos as a registration center

Register the newly created microservice to Nacos

two steps

  • Configure the application name, nacos discovery registration ip address, and port number in the configuration file

  • Use the @EnableDiscoveryClient annotation on the startup class to enable the registration function

Use Redis to store verification code information

Just add the dependent configuration address and port number

Direct injection into the StringRedisTemplate template class is

Use as follows

// Get the value in redis according to the key

String redisCode= redisTemplate.opsForValue().get(key value in redis);

// Store the value in Redis

redisTemplate.opsForValue().set(key value, value value, expiration time, expiration unit (TimeUnit.MINUTES));

Use gitee for social login (OAuth2.0) (highlight 1)

Here, because Weibo developer permission application is too slow, use gitee to realize social login

Steps to social login

(1) The front end calls a third-party application as a social login

This is different from the previous model. In the past, the front end directly sends requests to the backend, and then the backend processes the requests. This is to call the third-party application as a social login (that is, first jump to the login authorization page of gitee), and then the user Log in your own gitee account password to authorize. After the authorization is successful, it will jump to the specified application callback address, and then process the application callback address request on the backend

gitee developer background management link: https://gitee.com/oauth/applications/16285

Call the url address of gitee third-party login: <a href=" https://gitee.com/oauth/authorize?client_id= Client ID of your own application&redirect_uri=Successful callback address of your own application&response_type=code&state=1">

<li>
  <a href="https://gitee.com/oauth/authorize?client_id=32459f971ce6d89cfb9f70899525455d0653cb804f16b38a304e3447dc97d673&redirect_uri=http://auth.saodaimall.com/callback&response_type=code&state=1">
    <img style="width: 50px;height: 18px;margin-top: 35px;" src="/static/login/JD_img/gitee.png"/>
  </a>
</li>

(2) Social service OAuth2Controller to handle the application callback address/callback request

Sub-process: (In fact, there are only three lines of code that need to be configured by yourself (lines 42-44 of gitee of OAuth2Controller), and the others are basically fixed)

1> Encapsulate the AccessTokenDTO object and send it to the code cloud server. If the AccessTokenDTO object is correct, it will return an access_token pass token (in fact, a code will be returned after the user authorizes, and then the code and other information will be encapsulated into an AccessTokenDTO object. Find the code cloud server to obtain an access_token pass token, and finally use the access_token pass token to find the code cloud server to ask the user's public information on gitee)

2> After obtaining the access_token pass token, go to the code cloud server to get the user's public information and convert it into a general gitee social login GiteeUser object

3>Remotely call membership service for social login

package com.saodai.saodaimall.auth.controller;

import com.alibaba.fastjson.TypeReference;
import com.saodai.common.utils.R;
import com.saodai.common.vo.MemberResponseVo;
import com.saodai.saodaimall.auth.component.GitheeProvider;
import com.saodai.saodaimall.auth.feign.MemberFeignService;
import com.saodai.saodaimall.auth.vo.AccessTokenDTO;
import com.saodai.saodaimall.auth.vo.GiteeUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import javax.servlet.http.HttpSession;

import static com.saodai.common.constant.AuthServerConstant.LOGIN_USER;


/**
* Social third-party authorization login
**/

@Slf4j
@Controller
public class OAuth2Controller {
    
    @Autowired
    private MemberFeignService memberFeignService;
    @Autowired
    private AccessTokenDTO accessTokenDTO;
    @Autowired
    private GitheeProvider githeeProvider;
    
    @GetMapping(value = "/callback")
    // /callback?code=e867a1f4575d4a6161e3249423a0403898253bc593e4b031a8771739ee6769f5&state=1
    public String gitee(@RequestParam(name = "code") String code,@RequestParam(name = "state") String state, HttpSession session) throws Exception {
        System.out.println(code);
        //The following three lines of code are the values ​​of your own application, and you can see the corresponding value in the third-party application of gitee
        accessTokenDTO.setClient_id("32459f971ce6d89cfb9f70899525455d0653cb804f16b38a304e3447dc97d673");
        accessTokenDTO.setClient_secret("f3046c911c03cadcded986062708150d4232af3ca6aef0259e5a0198d2c15ba5");
        accessTokenDTO.setRedirect_uri("http://auth.saodaimall.com/callback");
        accessTokenDTO.setCode(code);
        accessTokenDTO.setState(state);
        String accessToken = githeeProvider.getAccessToken(accessTokenDTO);
        //2. Processing
        if (!StringUtils.isEmpty(accessToken)) {
            //Get the access_token and turn it into a general gitee social login object
            GiteeUser giteeUser = githeeProvider.getGiteeUser(accessToken);
            //Know which social user
            //1) If the current user enters the website for the first time, it will be automatically registered (a member information is generated for the current social user, and this social account will correspond to the designated member in the future)
            //Log in or register as a social user
            //call remote service
            R oauthLogin = memberFeignService.oauthLogin(giteeUser);
            if (oauthLogin.getCode() == 0) {
                MemberResponseVo memberResponseVo = oauthLogin.getData("data", new TypeReference<MemberResponseVo>() {});
                log.info("Successful login: User information:{}",memberResponseVo.toString());
                
                //1. When using the session for the first time, order the browser to save the card number and the JSESSIONID cookie
                //Which website the browser visits in the future will bring the cookie of this website
                //TODO 1. The token issued by default. Current domain (solve subdomain session sharing problem)
                //TODO 2. Use JSON serialization to serialize objects into Redis
                session.setAttribute(LOGIN_USER,memberResponseVo);
                
                //2. Log in successfully and jump back to the home page
                return "redirect:http://saodaimall.com";
            } else {
                
                return "redirect:http://auth.saodaimall.com/login.html";
            }
            
        } else {
            return "redirect:http://auth.saodaimall.com/login.html";
        }
        
    }
    
}
package com.saodai.saodaimall.auth.vo;


/**
 * AccessTokenDTO Object encapsulation (gitee social login token)
 */

import org.springframework.stereotype.Component;

@Component
public class AccessTokenDTO {
    private String client_id;
    private String client_secret;
    private String code;
    private String redirect_uri;
    private String state;

    public String getClient_id() {
        return client_id;
    }

    public void setClient_id(String client_id) {
        this.client_id = client_id;
    }

    public String getClient_secret() {
        return client_secret;
    }

    public void setClient_secret(String client_secret) {
        this.client_secret = client_secret;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getRedirect_uri() {
        return redirect_uri;
    }

    public void setRedirect_uri(String redirect_uri) {
        this.redirect_uri = redirect_uri;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }
}
package com.saodai.saodaimall.auth.component;


import com.alibaba.fastjson.JSON;
import com.saodai.saodaimall.auth.vo.AccessTokenDTO;
import com.saodai.saodaimall.auth.vo.GiteeUser;
import okhttp3.*;
import org.springframework.stereotype.Component;

import java.io.IOException;

/**
 * Request code cloud server
 */
@Component
public class GitheeProvider {

    //Initiate a post request to obtain AccessToken
    public String getAccessToken(AccessTokenDTO accessTokenDTO){
        MediaType mediaType= MediaType.get("application/json; charset=utf-8");
        OkHttpClient client = new OkHttpClient();
        RequestBody body = RequestBody.create(mediaType, JSON.toJSONString(accessTokenDTO));
        Request request = new Request.Builder()
                .url("https://gitee.com/oauth/token?grant_type=authorization_code&code="+accessTokenDTO.getCode()+
                        "&client_id="+accessTokenDTO.getClient_id()+"&redirect_uri="+accessTokenDTO.getRedirect_uri()+
                        "&client_secret="+accessTokenDTO.getClient_secret())
                .post(body)
                .build();
        try (Response response = client.newCall(request).execute()) {
            String string = response.body().string();
            System.out.println(string);
            String str1 = string.split(":")[1];
            String str2 = str1.split("\"")[1];
            return str2;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    //Initiate a get request to return the GitUser object,
    public GiteeUser getGiteeUser(String token){
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url("https://gitee.com/api/v5/user?access_token="+token)
                .build();
        try (Response response = client.newCall(request).execute()) {
            String string=response.body().string();
            GiteeUser giteeUser = JSON.parseObject(string, GiteeUser.class);
            return giteeUser;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}
package com.saodai.saodaimall.auth.vo;

import lombok.Data;

/**
 * GiteeUser Object encapsulation (gitee object for social login)
 */
@Data
public class GiteeUser {
    //gitee username
    private String name;
    //gitee-user-id
    private String id;
    //gitee user introduction
    private String bio;


}

(3) Remotely call member services for social login

/**
     * social login
     * @param giteeUser
     * @return
     * @throws Exception
     */
  @PostMapping(value = "/oauth2/login")
    public R oauthLogin(@RequestBody GiteeUser giteeUser) throws Exception {

        MemberEntity memberEntity = memberService.login(giteeUser);

        if (memberEntity != null) {
            return R.ok().setData(memberEntity);
        } else {
            return R.error(BizCodeEnum.LOGINACCT_PASSWORD_EXCEPTION.getCode(),BizCodeEnum.LOGINACCT_PASSWORD_EXCEPTION.getMessage());
        }
    }
 /**
     * social login
     * @param giteeUser
     * @return
     * @throws Exception
     */
    @Override
    public MemberEntity login(GiteeUser giteeUser) throws Exception {

        //Get gitee user unique id
        String giteeUserId = giteeUser.getId();

        //1. Determine whether the current social user has logged into the system
        MemberEntity memberEntity = this.baseMapper.selectOne(new QueryWrapper<MemberEntity>().eq("social_id", giteeUserId));
        //This user is already registered
        if (memberEntity != null) {
            return memberEntity;
        } else {
            //2. If there is no record corresponding to the current social user, we need to register one
            MemberEntity register = new MemberEntity();
            //Social gitee login id as member id
            register.setId(Long.valueOf(giteeUserId));
            register.setSocialName(giteeUser.getName());
            register.setUsername(giteeUser.getName());
            register.setNickname(giteeUser.getName());
            register.setCreateTime(new Date());
            register.setSocialBio(giteeUser.getBio());
            register.setSocialId(giteeUserId);
            //Insert user information into the database
            this.baseMapper.insert(register);
            return register;

        }

    }
package com.saodai.saodaimall.member.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;

/**
 * member
 */
@Data
@TableName("ums_member")
public class MemberEntity implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * id
	 */
	@TableId
	private Long id;
	/**
	 * membership level id
	 */
	private Long levelId;
	/**
	 * username
	 */
	private String username;
	/**
	 * password
	 */
	private String password;
	/**
	 * Nick name
	 */
	private String nickname;
	/**
	 * phone number
	 */
	private String mobile;
	/**
	 * Mail
	 */
	private String email;
	/**
	 * avatar
	 */
	private String header;
	/**
	 * gender
	 */
	private Integer gender;
	/**
	 * Birthday
	 */
	private Date birth;
	/**
	 * city
	 */
	private String city;
	/**
	 * Profession
	 */
	private String job;
	/**
	 * Signature
	 */
	private String sign;
	/**
	 * user source
	 */
	private Integer sourceType;
	/**
	 * integral
	 */
	private Integer integration;
	/**
	 * growth value
	 */
	private Integer growth;
	/**
	 * enabled state
	 */
	private Integer status;
	/**
	 * Registration time
	 */
	private Date createTime;

	/**
	 * ID of the social login user
	 */
	private String socialId;

	/**
	 * The name of the social login user
	 */
	private String socialName;

	/**
	 * Self-introduction for social login users
	 */
	private String socialBio;

}

Integrate SpringSession to solve the problem of session synchronization and non-sharing (highlight 2)

The purpose of using SpringSession is to solve the problem that distributed session s are not synchronized and not shared. Using SpringSession can store all sessions in redis, which solves the problem of session asynchrony, and then expands the scope, which solves the problem of not sharing sessions. SpringSession does not require explicit operations (that is, it does not need to use the StringRedisTemplate class. The method to put the session into redis, you don’t have to do anything, just put the data into HttpSession normally), because of the integration of SpringSession, the data put into HttpSession will be automatically put into redis, Since serialization is configured, the session will be serialized as a json string and put into redis, and then when a front-end service wants to fetch the session, it will also be automatically fetched from redis

Note: Since the type of springsession used here is redis, both springsession and redis must be added with dependencies and configurations (so the session will be stored in the Redis cache)

(1) Import dependencies

<!-- to integrate springsession to solve distributed session Out of sync, out of share problem-->
<dependency>
  <groupId>org.springframework.session</groupId>
  <artifactId>spring-session-data-redis</artifactId>
</dependency>
<!-- to integrate redis-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

(2) Configure springsession in the application.properties configuration file

#configure springsession
spring.session.store-type=redis
server.servlet.session.timeout=30m
#Configure the ip address of redis
spring.redis.host=192.168.241.128

(3) Add springSession configuration class in config configuration

package com.saodai.saodaimall.order.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;


/**
* springSession Configuration class (session configuration of all services to use session should be consistent)
*/

@Configuration
public class GulimallSessionConfig {
    
    /**
    * Configure session (mainly to enlarge the scope of session)
    * @return
    */
    @Bean
    public CookieSerializer cookieSerializer() {
        
        DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
        
        //Zoom in
        cookieSerializer.setDomainName("saodaimall.com");
        cookieSerializer.setCookieName("SAODAISESSION");
        
        return cookieSerializer;
    }
    
    
    /**
    * Configure the Session to be stored in redis in the format of json (actually json serialization)
    * @return
    */
    @Bean
    public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
        return new GenericJackson2JsonRedisSerializer();
    }
    
}

(4) Add the @EnableRedisHttpSession annotation to the startup class

package com.saodai.saodaimall.order;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

/**
 * Order service startup class
 */
@EnableFeignClients
@EnableRedisHttpSession
@EnableDiscoveryClient
@SpringBootApplication
public class SaodaimallOrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(SaodaimallOrderApplication.class, args);
    }

}

The principle of SpringSession

The implementation of Spring-Session is to design a filter SessionRepositoryFilter. Whenever a request comes in, the filter will first convert the two objects ServletRequest and ServletResponse into Spring's internal wrapper classes SessionRepositoryRequestWrapper and SessionRepositoryResponseWrapper objects, which use a SessionRepositoryRequestWrapper class to take over Http Session and overridden getSession method to achieve session creation and management. Transfer the process of creating a session by the web server to Spring-Session for creation. The originally created session is stored in the memory of the Web server, and the session information created by Spring-Session can be stored in a third-party service, such as: redis,mysql wait. Web servers share data by connecting to third-party services to achieve Session sharing!

@Order(SessionRepositoryFilter.DEFAULT_ORDER)
public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter {
    @Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        
		request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
         //Create a SessionRepositoryRequestWrapper using HttpServletRequest, HttpServletResponse and servletContext
		SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(
				request, response, this.servletContext);
		SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(
				wrappedRequest, response);
		try {
			filterChain.doFilter(wrappedRequest, wrappedResponse);
		}
		finally {
            //Save session information
			wrappedRequest.commitSession();
		}
	}
}
@Override
		public HttpSessionWrapper getSession(boolean create) {
            //Obtain the attributes representing the Session in the current Request scope, and the caching effect avoids obtaining it from the sessionRepository every time
			HttpSessionWrapper currentSession = getCurrentSession();
			if (currentSession != null) {
				return currentSession;
			}
            //Find a cookie called SESSION in the client, get the sessionId, and use the sessionRepository object to search in Redis according to the sessionId
			S requestedSession = getRequestedSession();
            //If the value is queried from redis
			if (requestedSession != null) {
                //The sessionId exists on the client and has not expired
				if (getAttribute(INVALID_SESSION_ID_ATTR) == null) {
					requestedSession.setLastAccessedTime(Instant.now());
					this.requestedSessionIdValid = true;
					currentSession = new HttpSessionWrapper(requestedSession, getServletContext());
					currentSession.setNew(false);
                    //Set the Session to the request attribute
					setCurrentSession(currentSession);
					return currentSession;
				}
			}
			else {
				// This is an invalid session id. No need to ask again if
				// request.getSession is invoked for the duration of this request
				if (SESSION_LOGGER.isDebugEnabled()) {
					SESSION_LOGGER.debug(
							"No session found by id: Caching result for getSession(false) for this HttpServletRequest.");
				}
				setAttribute(INVALID_SESSION_ID_ATTR, "true");
			}
            //Return null directly without creating a Session
			if (!create) {
				return null;
			}
			if (SESSION_LOGGER.isDebugEnabled()) {
				SESSION_LOGGER.debug(
						"A new session was created. To help you troubleshoot where the session was created we provided a StackTrace (this is not an error). You can prevent this from appearing by disabling DEBUG logging for "
								+ SESSION_LOGGER_NAME,
						new RuntimeException(
								"For debugging purposes only (not an error)"));
			}
            //Execution to this point indicates that a new Session needs to be created
            // Create the RedisSession object through sessionRepository
			S session = SessionRepositoryFilter.this.sessionRepository.createSession();
			session.setLastAccessedTime(Instant.now());
			currentSession = new HttpSessionWrapper(session, getServletContext());
			setCurrentSession(currentSession);
			return currentSession;
		}
	// Create the RedisSession object through sessionRepository
	@Override
	public RedisSession createSession() {
		Duration maxInactiveInterval = Duration
				.ofSeconds((this.defaultMaxInactiveInterval != null)
						? this.defaultMaxInactiveInterval
						: MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
		RedisSession session = new RedisSession(maxInactiveInterval);
		session.flushImmediateIfNecessary();
		return session;
	}

Good article reference: Spring-Session realizes the principle and analysis of session sharing

small technology

Use view mapper to realize page jump

The traditional way of writing is to implement in the controller

Customize a configuration class to implement the WebMvcConfigurer interface, and then override the addViewControllers method to add the view mapper

Use configuration files to dynamically configure property values

In this way, the attribute value can be changed by modifying the corresponding value in the configuration file. The core annotation is @ConfigurationProperties (prefix=""), and the @Data annotation should also be added

Use Feign to call services remotely

Add the openFeign dependency and start the remote caller via the @EnableFeignClients annotation to use the feign remote call service

Define a remote call interface, use the @FeignClient annotation to specify which service to call, just take the method signature of the third-party service controller, pay attention to the path must be written correctly, especially if there is a parent path, don’t forget to write

Inject the newly written remote interface into the server that needs to be called remotely, and then call it. For example, here is the interface where the authentication center calls the third-party service to generate the verification code.

Use the exception mechanism

 /**
     *    Sign Up
     */

    @Override
    public void register(MemberUserRegisterVo vo) {

        MemberEntity memberEntity = new MemberEntity();

        //set default level
        MemberLevelEntity levelEntity = memberLevelDao.getDefaultLevel();
        memberEntity.setLevelId(levelEntity.getId());

        //Set other default information
        //Check if username and phone number are unique. Perception exception, exception mechanism (the exception mechanism is to throw a specific exception if there is a problem, and continue to execute the following statement if there is no problem)
        checkPhoneUnique(vo.getPhone());
        checkUserNameUnique(vo.getUserName());

        memberEntity.setNickname(vo.getUserName());
        memberEntity.setUsername(vo.getUserName());
        //The password is encrypted with MD5 salt value (the result of each encryption of the same data encrypted by salt value is different, and the password is verified by the match method)
        // (Note that md5 cannot be used to encrypt the database directly, because the rainbow table can crack md5, the so-called rainbow table is to reversely exit md5 through a large amount of md5 data
        // Note that MD5 is irreversible, but it can be cracked violently through the rainbow table)
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        String encode = bCryptPasswordEncoder.encode(vo.getPassword());
        memberEntity.setPassword(encode);
        memberEntity.setMobile(vo.getPhone());
        memberEntity.setGender(0);
        memberEntity.setCreateTime(new Date());

        //save data
        this.baseMapper.insert(memberEntity);
    }

    /**
     * An abnormal mechanism method to check whether the mobile phone number is duplicated
     * @param phone
     * @throws PhoneException
     */
    @Override
    public void checkPhoneUnique(String phone) throws PhoneException {

        Long phoneCount = this.baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("mobile", phone));
        //usernameCount > 0 means the phone number already exists
        if (phoneCount > 0) {
            throw new PhoneException();
        }

    }

    /**
     * Exception mechanism method to check whether username is duplicate
     * @param userName
     * @throws UsernameException
     */
    @Override
    public void checkUserNameUnique(String userName) throws UsernameException {

        Long usernameCount = this.baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("username", userName));
        //usernameCount > 0 means the username already exists
        if (usernameCount > 0) {
            throw new UsernameException();
        }
    }

The specific implementation of the exception mechanism method defined above to check the user name and phone number

  /**
     * Member registration function
     * @param vo
     * @return
     */
    @PostMapping(value = "/register")
    public R register(@RequestBody MemberUserRegisterVo vo) {

        try {
            memberService.register(vo);
        } catch (PhoneException e) {
            //BizCodeEnum.PHONE_EXIST_EXCEPTION=The same phone number exists 15002
            return R.error(BizCodeEnum.PHONE_EXIST_EXCEPTION.getCode(),BizCodeEnum.PHONE_EXIST_EXCEPTION.getMessage());
        } catch (UsernameException e) {
            //BizCodeEnum.USER_EXIST_EXCEPTION=Product inventory is insufficient 21000
            return R.error(BizCodeEnum.USER_EXIST_EXCEPTION.getCode(),BizCodeEnum.USER_EXIST_EXCEPTION.getMessage());
        }

        return R.ok();
    }

The reason for using the exception mechanism is to hope that the controller can detect and handle exceptions

package com.saodai.saodaimall.member.exception;


public class UsernameException extends RuntimeException {
    
    
    public UsernameException() {
        super("same username exists");
    }
}
package com.saodai.saodaimall.member.exception;


public class PhoneException extends RuntimeException {

    public PhoneException() {
        super("same phone number exists");
    }
}

Extract the above two individual exceptions and encapsulate them into exception classes

Encrypt with MD5 salt value

encryption

First create an encryptor BCryptPasswordEncoder, then call its encode method to put the password to be encrypted, and a string of encrypted values ​​will be automatically generated

Note that the generated value of the same password is different each time

decrypt

After getting the encrypted data from the database, call the matches method to match whether the two passwords are consistent. If they are consistent, return true, and if they are inconsistent, return false. The previous password is the old password (unencrypted password), and the latter passwordDb is the database. encrypted password

Tags: Java Redis Cache

Posted by jefkin on Fri, 10 Feb 2023 01:27:45 +0530