SSO single sign on

Time:2022-5-1

There are three common ways of single sign on:

1.1. Single server mode

Early single server, user authentication. (session broadcast mechanism: session replication)
SSO single sign on
Disadvantages: single point performance pressure, unable to expand

1.2. SSO (single sign on) mode

Distributed, SSO (single sign on) mode
SSO single sign on

1. After any module of the project logs in, put the data in two places: redis + cookie
(1) Redis: generate a unique random value (IP, user ID) in the key, and store user data in the value.
(2) Put the value of the generated key in the cookie: redis.
2. Visit other modules in the project, send the request with the cookie, get the cookie to send, get the cookie value, and take the cookie to redis for query.

advantage:
Independent management of user identity information, better distributed management.
You can extend the security policy yourself
Disadvantages:
The access pressure of authentication server is high.

1.3. Token Mode

Business process diagram {process that users must log in when accessing business}
SSO single sign on
advantage:
Stateless: token stateless and session stateless
Based on Standardization: your API can adopt standardized JSON web token (JWT)
Disadvantages:
Occupied bandwidth
Cannot destroy on server side
Note: Based on microservice development, there are relatively many forms of token, so I use token as the standard of user authentication

Cross domain authentication using JWT

Reasons for using JWT:
(1). Traditional authentication:

Internet services cannot be separated from user authentication. The general process is as follows:
1. The user sends the user name and password to the server.
2. After verifying the server, relevant data (such as user role, login time, etc.) will be saved in the current session.
3. The server returns a session to the user_ ID and session information will be written to the user's cookie.
4. Each subsequent request of the user will be made by taking out the session in the cookie_ Pass the ID to the server.
5. The server received a session_ ID and compare the previously saved data to confirm the user's identity.
The biggest problem with this model is that there is no distributed architecture and it cannot support horizontal expansion.

(2). Using a self-contained token, data is saved through the client, while the server does not save session data. JWT is the representative of this solution.

JWT can be used not only for authentication, but also for information exchange. Making good use of JWT can help reduce the number of times the server requests the database.
The produced token can contain basic information, such as ID, user nickname, avatar and other information, so as to avoid checking the database again
It is stored on the client side and does not occupy the memory resources of the server side
JWT does not encrypt by default, but can encrypt. After the original token is generated, it can be encrypted again.
When JWT is not encrypted, some private data cannot be transmitted through JWT.
The biggest disadvantage of JWT is that the server does not save the session state, so it is impossible to cancel the token or change the permission of the token during use. Also
That is, once the JWT is issued, it will remain valid during the validity period.
JWT itself contains authentication information. The token is Base64 encoded, so it can be decoded. Therefore, the object before token encryption should not be encrypted
It contains sensitive information. Once the information is leaked, anyone can get all the permissions of the token. In order to reduce embezzlement, the validity period of JWT is not limited
It should be set too long. For some important operations, users should authenticate every time they use it.
In order to reduce embezzlement and theft, JWT does not recommend using HTTP protocol to transmit code, but uses encrypted HTTPS protocol to transmit code
Lose.

Usage of JWT:

The client receives the JWT returned by the server and stores it in cookie or localstorage.
After that, the client will bring JWT in the interaction with the server. If it is stored in a cookie, it can be sent automatically, but it will not cross domains. Therefore, it is generally placed in the header authorization field of the HTTP request. When cross domain, JWT can also be placed in the data body of post request.

Integrated JWT

Import dependency:

<dependencies>
<!-- JWT-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
</dependencies>

Create JWT tool class:

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;

/**
 * @author sun
 * 
 */
public class JwtUtils {

    //Constant
    public static final long EXPIRE = 1000 * 60 * 60 * 24; // Token expiration time
    public static final String APP_ SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO"; // Secret key

    //Method of generating token string
    public static String getJwtToken(String id, String nickname){

        String JwtToken = Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")

                .setSubject("guli-user")
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))

                . claim ("Id", ID) // set the token body to store user information
                .claim("nickname", nickname)
				//Set encryption method
                .signWith(SignatureAlgorithm.HS256, APP_SECRET)
                .compact();

        return JwtToken;
    }

    /**
     *Judge whether the token exists and is valid
     * @param jwtToken
     * @return
     */
    public static boolean checkToken(String jwtToken) {
        if(StringUtils.isEmpty(jwtToken)) return false;
        try {
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     *Judge whether the token exists and is valid
     * @param request
     * @return
     */
    public static boolean checkToken(HttpServletRequest request) {
        try {
            String jwtToken = request.getHeader("token");
            if(StringUtils.isEmpty(jwtToken)) return false;
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     *Get the member ID according to the token string
     * @param request
     * @return
     */
    public static String getMemberIdByJwtToken(HttpServletRequest request) {
        String jwtToken = request.getHeader("token");
        if(StringUtils.isEmpty(jwtToken)) return "";
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        Claims claims = claimsJws.getBody();
        return (String)claims.get("id");
    }
}

MD5 tools:

public final class MD5 {

    public static String encrypt(String strSrc) {
        try {
            char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
                    '9', 'a', 'b', 'c', 'd', 'e', 'f' };
            byte[] bytes = strSrc.getBytes();
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(bytes);
            bytes = md.digest();
            int j = bytes.length;
            char[] chars = new char[j * 2];
            int k = 0;
            for (int i = 0; i < bytes.length; i++) {
                byte b = bytes[i];
                chars[k++] = hexChars[b >>> 4 & 0xf];
                chars[k++] = hexChars[b & 0xf];
            }
            return new String(chars);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            Throw new runtimeException ("MD5 encryption error!! +" + e);
        }
    }

    public static void main(String[] args) {
        System.out.println(MD5.encrypt("111111"));
    }

}

Write configuration class:

#Service port
server.port=8005
#Service name
spring.application.name=service-msm

#Mysql database connection
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456

spring. redis. Host = IP address
spring.redis.port=6379
spring.redis.database= 0
spring. redis. Password = password
spring.redis.timeout=1800000

spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-wait=-1
#Maximum blocking waiting time (negative number means no limit)
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0
#Minimum idle

#Returns the global time format of JSON
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8


#Mybatis log
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

Test interface

Login to generate token

Controller: encrypt the user ID and user name with token

@Apioperation (value = "member login")
@PostMapping("login")
public R login(@RequestBody LoginVo loginVo) {
String token = memberService.login(loginVo);
return R.ok().data("token", token);
 }

service:

@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
*Member login
* @param loginVo
* @return
*/
@Override
public String login(LoginVo loginVo) {
String mobile = loginVo.getMobile();
String password = loginVo.getPassword();
//Calibration parameters
if(StringUtils.isEmpty(mobile) ||
StringUtils.isEmpty(password) ||
StringUtils.isEmpty(mobile)) {
throw new GuliException(20001,"error");
 }
//Get members
Member member = baseMapper.selectOne(new
QueryWrapper<Member>().eq("mobile", mobile));
if(null == member) {
throw new GuliException(20001,"error");
 }
//Check password
if(!MD5.encrypt(password).equals(member.getPassword())) {
throw new GuliException(20001,"error");
 }
//Check whether it is disabled
if(member.getIsDisabled()) {
throw new GuliException(20001,"error");
 }
//Generate token string using JWT
String token = JwtUtils.getJwtToken(member.getId(), member.getNickname());
return token;
 }

Obtain user information according to the token:

@Apioperation (value = "get login information according to token")
@GetMapping("auth/getLoginInfo")
public R getLoginInfo(HttpServletRequest request){
try {
String memberId = JwtUtils.getMemberIdByJwtToken(request);
LoginInfoVo loginInfoVo = memberService.getLoginInfo(memberId);
return R.ok().data("item", loginInfoVo);
 }catch (Exception e){
e.printStackTrace();
throw new GuliException(20001,"error");
 }
 }

Service: check the user information in the database according to the resolved ID:

@Override
public LoginInfoVo getLoginInfo(String memberId) {
Member member = baseMapper.selectById(memberId);
LoginInfoVo loginInfoVo = new LoginInfoVo();
BeanUtils.copyProperties(member, loginInfoVo);
return loginInfoVo;
 }

Instead of comparing the sessionid with the sessionid in the server separately, the user information can be obtained directly by including the user name, avatar and other basic information in the token (the password and other private information cannot be included. If the user’s private information is required, it can be queried directly through the parsed user information)