Actual combat of microservice scenario: build authentication center service from scratch based on SpringCloud Alibaba

Posted by hush on Tue, 07 Dec 2021 21:25:17 +0100

Authentication center service

Meet JWT

json web token is an open standard. It defines a compact and self-contained way to safely transmit information between parties as json objects

  • After authentication, the server will generate a json object and send it to the client. After that, both the client and the server need to bring this object to transmit data. The server fully recognizes the client's identity through this json object. In order to prevent tampering with data, the server will add a signature (encryption) when generating it. The server does not save session data, that is, stateless, More suitable for expansion
  • Which environments can consider using jwt? User authorization, information exchange

JWT components

Header: header information

The Header consists of two parts (Token type, name of encryption algorithm) and uses base64 encoding

Payload: the data we want to transfer

Poetry data in the form of Payload KV, here is the information we want to transmit (Token information if authorized)

Signature: Signature

Signature in order to get a signature, we must first have an encoded header, an encoded payload and a key. The algorithm used for signing is the one specified in the header, and then they will be signed

We need a signature formula

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

Generate a signature, return a string and return it to the client. After that, the client will bring this string for authentication every time it accesses

JWT uses. Number to connect HHH.PPPP.SSSS

Authorization, authentication design

We will not consider the gateway here, but will build it later. We will focus on the middle and right parts

In the authentication part, we independently implement the public tool class. Why? The following three points

  1. JWT is essentially an encrypted string calculated by the algorithm, which can also be inversely parsed by the algorithm. It does not rely on any framework, so this function has the premise that it can be extracted separately
  2. Our e-commerce system contains multiple micro services. Obviously, each service needs authentication, so we extract this method for reuse
  3. For high-performance authentication, why not do authentication in the authorization center? First, he turns back to a series of operations such as http requests. If we only use java locally, many steps are omitted, and the performance is multiplied

Implementation of authorization code

We create a new service to write our authentication center

e-commerce-authority-center

Import related dependencies

    <dependencies>
        <!-- spring cloud alibaba nacos discovery rely on -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>2.2.3.RELEASE</version>
        </dependency>
        <!-- Java Persistence API, ORM standard -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!-- MySQL drive, be careful, This need and MySQL Version correspondence -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.12</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.hyc.ecommerce</groupId>
            <artifactId>e-commerce-mvc-config</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!-- zipkin = spring-cloud-starter-sleuth + spring-cloud-sleuth-zipkin-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.kafka</groupId>
            <artifactId>spring-kafka</artifactId>
            <version>2.5.0.RELEASE</version>
        </dependency>
        <!-- screw Generate database document -->
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.30</version>
        </dependency>
        <dependency>
            <groupId>cn.smallbun.screw</groupId>
            <artifactId>screw-core</artifactId>
            <version>1.0.3</version>
        </dependency>
    </dependencies>

After importing the dependency, we write the corresponding configuration, such as registering with naocs, joining the supervision of adminserver, configuring data sources, etc. Here we use jpa to do orm

Configuration writing

server:
  port: 7000
  servlet:
    context-path: /ecommerce-authority-center

spring:
  application:
    name: e-commerce-authority-center
  cloud:
    nacos:
      discovery:
        enabled: true # If you do not want to use Nacos for service registration and discovery, set it to false
        server-addr: 127.0.0.1:8848 # Nacos server address
        # server-addr: 127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850 # Nacos server address
        namespace: 1bc13fd5-843b-4ac0-aa55-695c25bc0ac6
        metadata:
          management:
            context-path: ${server.servlet.context-path}/actuator
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: none
    properties:
      hibernate.show_sql: true
      hibernate.format_sql: true
    open-in-view: false
  datasource:
    # data source
    url: jdbc:mysql://127.0.0.1:3306/imooc_e_commerce?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
    username: root
    password: root
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    # Connection pool
    hikari:
      maximum-pool-size: 8
      minimum-idle: 4
      idle-timeout: 30000
      connection-timeout: 30000
      max-lifetime: 45000
      auto-commit: true
      pool-name: ImoocEcommerceHikariCP
  kafka:
    bootstrap-servers: 127.0.0.1:9092
    producer:
      retries: 3
    consumer:
      auto-offset-reset: latest
  zipkin:
    sender:
      type: kafka # The default is web
    base-url: http://127.0.0.1:9411/

# Exposure endpoint
management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
    health:
      show-details: always

After the configuration is completed, write the main startup class @ EnableJpaAuditing. Because we use the automatic join creation time and modification time, we need to turn on the automatic audit function of jpa, otherwise an error will be reported

@EnableJpaAuditing //Allow automatic audit of jpa
@SpringBootApplication
@EnableDiscoveryClient
public class AuthorityApplication {
    public static void main(String[] args) {
        SpringApplication.run(AuthorityApplication.class, args);

    }
}

Whether the test environment is correct under the test package

/**
 * Authorization center test entrance
 * Verify the availability of the authorization center environment
 */
@SpringBootTest
@RunWith(SpringRunner.class)
public class AuthorityCenterApplicationTest {

    @Test
    public void conetextLoad() {

    }
}

After the environment is ok

Let's test whether the database operation is available

Write entity class ecommerceUser

/*
 * User table entity class definition
 * */
@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "t_ecommerce_user")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class EcommerceUser {
    /* Self increasing assembly*/
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private long id;

    /*user name*/
    @Column(name = "username", nullable = false)

    private String username;

    /* MD5 password*/
    @Column(name = "password", nullable = false)
    private String password;

    /*Additional information json string storage*/
    @Column(name = "extra_info", nullable = false)
    private String extraInfo;

    /*Automatically add the annotation of the main startup class required for the creation time*/
    @CreatedDate
    @Column(name = "create_time", nullable = false)
    private Date createTime;

    /*Adding the update time automatically requires the annotation of the main startup class*/
    @CreatedDate
    @Column(name = "update_time", nullable = false)
    private Date updateTime;
}

With entity classes, we need to implement data operations, so we write Dao interfaces

In fact, when we create the interface, jpa already has the corresponding basic addition, deletion, modification and query methods

Here we implement two custom query methods

/**
 * EcommerceUserDao Interface definition
 */
public interface EcommerceUserDao extends JpaRepository<EcommerceUser, Long> {

    /*
     * Query EcommerceUser object by user name
     * Equal to select * form t_ecommerce_user where username=?
     * */
    EcommerceUser findByUsername(String name);

    /*
     * Query EcommerceUser object by user name
     * Equal to select * form t_ecommerce_user where username=? and password=?
     * */
    EcommerceUser findByUsernameAndPassword(String name, String password);

}

Then create the test service

/**
 * @author : Leng Huanyuan
 * @date : 2021/12/4
 * @context: EcommerceUser Related tests
 * @params :  null
 * @return :  * @return : null
 */
@SpringBootTest
@RunWith(SpringRunner.class)
@Slf4j
public class EcommerUserTest {
    @Autowired
    EcommerceUserDao ecommerceUserDao;

    /*Add a user data to the test */
    @Test
    public void createUserRecord() {
        EcommerceUser ecommerceUser = new EcommerceUser();
        //Set information to insert
        ecommerceUser.setUsername("hyc@qq.com");
        ecommerceUser.setPassword(MD5.create().digestHex("123456"));
        ecommerceUser.setExtraInfo("{}");
        //Log print return results
        log.info("server user:[{}]", JSON.toJSON(ecommerceUserDao.save(ecommerceUser)));
    }

    /*Test the custom method we wrote to query the new role we just created*/
    @Test
    public void SelectUserInfo() {
        String username = "hyc@qq.com";
        log.info("select userinof:[{}]", JSON.toJSON(ecommerceUserDao.findByUsername(username)));
    }
}

Test related methods: add users or query users according to conditions, and the tests pass

Asymmetric encryption algorithm for generating public and private keys of RSA256

He completes the verification by encrypting the private key and decrypting the public key. At present, many authentication algorithms are encrypted and authenticated by JWTRSA256. If you don't know much, you can use RSA256

code

Write a test class for generating public key and create some commonly used VO objects to store some commonly used variables, such as user information, public key, key and some commonly used attributes into the VO model

@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
/**
 *
 * @author : Leng Huanyuan
 * @date : 2021/12/5
 * @context: RSA Asymmetric encryption algorithm
 * @params :  null 
 * @return :  * @return : null
 */
public class RSATest {
    @Test
    public void generateKeyBytes() throws Exception {
        /*Get RSA algorithm instance*/
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        /* The minimum value here is 2048. If it is lower than, an error will be reported*/
        keyPairGenerator.initialize(2048);
        /*
         * Generate public key pair
         * */
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        /*Get public and private key objects*/
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        log.info("private key:[{}]", Base64.encode(privateKey.getEncoded()));
        log.info("public key:[{}]", Base64.encode(publicKey.getEncoded()));
    }
}

Create VO objects to save values and objects that we often use and will not change

The stored private key should be a private key, so it is only exposed to the authentication center. Therefore, we create a Constant package in the authentication service and create this AuthotityConstant class to save information

/**
 * @author : Leng Huanyuan
 * @date : 2021/12/5
 * @context: Constant of authentication
 * @params :  null
 * @return :  * @return : null
 */
public class AuthorCanstant {
    /*The private key is only exposed to the authentication center and not to any other service*/
    public static final String PRIVATE_KEY = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBA" +
            "QCMXrQCudalKHJlH16YHr9mI5/xyYnkp5u2gAbMFf2xAHAyykYmixJP3CqG2a8tUwiJjjTIJXP+79Jzgjgg" +
            "VbBaTakrvjeFXz9HNP1D4XD6Li+sRVjnN1iBUwIFRxiFN2EOJflA9bqeQLAge/LgAu06y3jdLLleJF7yDRuMH" +
            "YedqPl9AJa5RdJmt0OgCoVOqacB7oGkFCFISm0Cwjfgq06nyiiULGZNVt8uhDxZAE4Pi2lmf3yggXCBH9AtU/2" +
            "XdyxU9caQJOAbYGxd/mART/NivBjSqo60wcBnktI+booUbDKRBbWRxvfYqKWEwPOwxlJUB3l3pcLZm866Xl3qtVM" +
            "XAgMBAAECggEADCGjLRkik+OK/3JWmo8Nu6YYjKz+XeSecIdgDwNXiZSgHcOdjHc4fe5pPn5RxXkHo9vGdAXIoJ/Z" +
            "cGIwt5qwQx2zITSvV7eDoIPT36n8OaMEO79Cj7kYzRR/eDVMyTagDLj7ccHK/yJYFnaf5vxZxFsRdwwGeTxreD" +
            "/pwZJLxjRSz1W57v5yUJNPPimNB229EogNYHIhQ8+Z7OGiilbtBIL9r6lqlz2hUAVBzXl4kOXFVI+vEodLuV2" +
            "rtQXXrpO1+AgH5lZJ7ahShKbqHt/Q6uJSTKAhbsfv/iadcPjmYp2F7nnYBLf66Jln6AWUwnXrJ7XETOf/+Qcib" +
            "q/5m6RjAQKBgQDruxn+kaDr5uYQMVSHog+CBRBJghJ4JklhY7ZDYJ2wN2KNHOd3mW/wUVDihVIyRFniIzsWU" +
            "0lnI+4OLqNLAZOBaQB5VrjyH4fxn5b26t0xLO1d5EWcOYI8ZRhwWDWaZipe2dUMeqVVMYFeDdTdNsyGrf8x" +
            "L+OVyRDiH4s4pBIs7QKBgQCYcIVFgDbrmwsP7lA9/dU9kClutY3gjEUgB2IJp2Y8S4Xhfi4NC8GqRQoMUyuqg" +
            "vPHKEiTCa1EojGHS/+r4JVcSg9Wsv64SpGZ+gANxRhfYFPrbkjU4YOMaZeCGUfKR2QnD20c3I4gdQ9kU5nK52n+Y" +
            "JEkAFUejg1Mhb6Fp6HDkwKBgAHYYBa3CxxtnUVpLXE2Woq5AWyh4QUhv5dMkYOrgPB9Ln9OR52PDOpDqK9tP" +
            "bx4/n8fqXm+QyfUhyuDP/H5XC86JC/O9vmmN4kzp5ndMsgMwvrmK4lShet1GyDd/+VqgVBmwh0r5JlrHske" +
            "sJjesfEn8YRwDIcCoOg0OQHDfwTtAoGAQfE61YvXNihFqsiOkaKCYjVAlxGWpDJJnMdU05REl4ScD6WDy" +
            "kTxq/RdmmNIGmS3i8mTS3f+Khh3kG2B1ho6wkePRxP7OEGZpqAM8ef22RtUch2tB9neDBmJXtAMzCYB3xu/O" +
            "aL3IHdDB0Va2/krUsz3PDmgmK0ed6HLfwm64l0CgYB+iGkMAQEwqYmcCEXKK825Q9y/u8PE9y8uaMGfsZQzDo6v" +
            "V5v+reOhmZRrk5BnX+pgztbE28sS6c2vYR0RYoR90aD2GXungCPXWEMDQudHFxvSsNTCYkDynjTSlnzu9aDcfqw1" +
            "UIzHog2zCquSro7tnbOMsvV5UdsLBq+WNQGgAw==";

    /*The default token timeout is one day*/
    public static final Integer DEFAULT_EXPIRE_DAY = 1;
}

Then create some common VO models e-commerce-common

After saving the public key to the public package, our services need to be authorized and used

/**
 * @author : Leng Huanyuan
 * @date : 2021/12/5
 * @context: Constant definition of general module
 * @params :  null
 * @return :  * @return : null
 */
public class CommonCanstant {
    /* RSA Public key*/
    public static final String PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjF60ArnWpShyZ" +
            "R9emB6/ZiOf8cmJ5KebtoAGzBX9sQBwMspGJosST9wqhtmvLVMIiY40yCVz/u/Sc4I4IFWwWk2pK743hV8/RzT9Q+F" +
            "w+i4vrEVY5zdYgVMCBUcYhTdhDiX5QPW6nkCwIHvy4ALtOst43Sy5XiRe8g0bjB2Hnaj5fQCWuUXSZrdDoAqFTqmnA" +
            "e6BpBQhSEptAsI34KtOp8oolCxmTVbfLoQ8WQBOD4tpZn98oIFwgR/QLVP9l3csVPXGkCTgG2BsXf5gEU/zYrwY0qqO" +
            "tMHAZ5LSPm6KFGwykQW1kcb32KilhMDzsMZSVAd5d6XC2ZvOul5d6rVTFwIDAQAB";

    /* JWT Store user information in key*/
    public static final String JWT_USER_INFO_KEY = "e-commerce-user";
    /*Service ID of authorization center*/
    public static final String AUTHORITY_CENTER_SERVICE_ID = "e-commerce-authity-center";
}

Common VO objects of user information

  • JwtToken
  • LoginUserinfo
  • UsernameAndPassword
/**
 * @author : Leng Huanyuan
 * @date : 2021/12/5
 * @context: The token given to the client after authentication by the authorization center
 * @params :  null
 * @return :  * @return : null
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class JwtToken {
    /* JWT*/
    private String token;


}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUserinfo {
    /*User id*/
    private Long id;
    /*user name*/
    private String username;
}

/**
 * @author : Leng Huanyuan
 * @date : 2021/12/5
 * @context:User name and password
 * @params :  null
 * @return :  * @return : null
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UsernameAndPassword {
    /*user name */
    private String username;
    /*password*/
    private String password;
}

Authorization service writing

First, create an interface IJWTService

Define the authorization method we need to implement

/**
 * @author : Leng Huanyuan
 * @date : 2021/12/5
 * @context: JWT Related service interface definitions
 * @params :  null
 * @return :  * @return : null
 */
public interface IJWTService {

    /*
     * The default timeout is used to generate the token
     * */
    String generateToken(String username, String password) throws Exception;

    /*
     * Timeout time can be set for generating JWT Token. The unit is days
     * */
    String generateToken(String username, String password, Integer expireTime) throws Exception;


    /*
     * Register the user and generate a token return
     * */
    String registerUserAndGenerateToken(UsernameAndPassword usernameAndPassword) throws Exception;
}

Authorization method implementation class

Here we have three ways to implement it

  • Generate token for default timeout
  • Generate a token by customizing the timeout setting
  • Register a new user and return the generated token

JWT object generation details:

1) We need to set the object to be passed

2) We need to set a non duplicate id

3) We need to set the timeout

4) Set up our encrypted signature

5) Return string object after setting

Jwts.builder()
                //claim here is actually jwt's payload object -- > kV
                .claim(CommonCanstant.JWT_USER_INFO_KEY, JSON.toJSONString(loginUserinfo))
                // jwt id indicates the id of jwt
                .setId(UUID.randomUUID().toString())
                //Expiration time of jwt
                .setExpiration(expireDate)
                // Here is to set the private key and encryption type of encryption
                .signWith(getPrivateKey(), SignatureAlgorithm.RS256)
                //The generated jwt information returns a string type
                .compact();
    }

Complete code

@Service
@Slf4j
@Transactional(rollbackFor = Exception.class)
public class IJWTServiceIpml implements IJWTService {

    @Autowired
    private EcommerceUserDao ecommerceUserDao;

    @Override

    public String generateToken(String username, String password) throws Exception {
        return generateToken(username, password, 0);
    }

    @Override
    public String generateToken(String username, String password, Integer expireTime) throws Exception {
        //First, you need to verify whether the user has passed the authorization verification, that is, whether the entered user name and password can find the records matching the data table
        EcommerceUser ecommerceUser = ecommerceUserDao.findByUsernameAndPassword(username, password);
        if (ecommerceUser == null) {
            log.error("can not find user: [{}],[{}]", username, password);
            return null;
        }

        //Insert the object into the Token, that is, the object stored in JWT. The back end can know the user is operating after getting this information
        LoginUserinfo loginUserinfo = new LoginUserinfo(
                ecommerceUser.getId(), ecommerceUser.getUsername()
        );

        if (expireTime <= 0) {
            expireTime = AuthorCanstant.DEFAULT_EXPIRE_DAY;
        }
        //Calculate timeout
        ZonedDateTime zdt = LocalDate.now().plus(expireTime, ChronoUnit.DAYS)
                .atStartOfDay(ZoneId.systemDefault());
        Date expireDate = Date.from(zdt.toInstant());

        return Jwts.builder()
                //claim here is actually jwt's payload object -- > kV
                .claim(CommonCanstant.JWT_USER_INFO_KEY, JSON.toJSONString(loginUserinfo))
                // jwt id indicates the id of jwt
                .setId(UUID.randomUUID().toString())
                //Expiration time of jwt
                .setExpiration(expireDate)
                // Here is to set the private key and encryption type of encryption
                .signWith(getPrivateKey(), SignatureAlgorithm.RS256)
                //The generated jwt information returns a string type
                .compact();
    }

    @Override
    public String registerUserAndGenerateToken(UsernameAndPassword usernameAndPassword) throws Exception {
        //First check whether the user name exists. If it exists, it cannot be registered again
        EcommerceUser oldUser = ecommerceUserDao.findByUsername(usernameAndPassword.getUsername());
        if (null != oldUser) {
            log.error("username is registered:[{}]", oldUser.getUsername());
            return null;
        }
        EcommerceUser ecommerceUser = new EcommerceUser();
        ecommerceUser.setUsername(usernameAndPassword.getUsername());
        ecommerceUser.setPassword(usernameAndPassword.getPassword()); //After MD5 coding
        ecommerceUser.setExtraInfo("{}");

        //Register a new user and write it to a record table
        ecommerceUser = ecommerceUserDao.save(ecommerceUser);

        log.info("regiter user success:[{}],[{}]", ecommerceUser.getUsername());

        //Generate a token and return
        return generateToken(ecommerceUser.getUsername(), ecommerceUser.getPassword());
    }

    /*
     * Obtain the PrivateKey object according to the locally stored private key
     * */
    private PrivateKey getPrivateKey() throws Exception {

        //Create a new pkcs8encoded keyspec with the given encoding key.
        PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(new BASE64Decoder().decodeBuffer(AuthorCanstant.PRIVATE_KEY));
        // Set the factory encryption method for generating new keys
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        //Return the generated key
        return keyFactory.generatePrivate(priPKCS8);
    }
}

After that, our authorization will use the above methods

Controller

We need to give a program entry for registering users and generating token s

It's our AuthorityController. The annotation @ IgnoreResponseAdvice we used before can be used here. Why don't we let it be encapsulated? We need to verify it. A simple JwtToken object can be used without encapsulation and transformation

@Slf4j
@RestController
@RequestMapping("/authority")
public class AuthorityConroller {
    private final IJWTService ljwtService;

    public AuthorityConroller(IJWTService ljwtService) {
        this.ljwtService = ljwtService;
    }

    /*
     * The package that obtains the token (actually the login function) from the authorization center and there is no unified response in the returned information
     * */
    @IgnoreResponseAdvice
    @PostMapping("/token")
    public JwtToken token(@RequestBody UsernameAndPassword usernameAndPassword) throws Exception {
        //Usually, the log does not print the user's information to prevent disclosure. We are an authorized server and are not open to the public, so we can print the user's information to the log for easy viewing
        log.info("request to get token with param:[{}]", JSON.toJSONString(usernameAndPassword));
        return new JwtToken(ljwtService.generateToken(
                usernameAndPassword.getUsername(),
                usernameAndPassword.getPassword()));
    }

    /*Registering a user and returning the token to register the current user is a common user through the authorization center*/
    @IgnoreResponseAdvice
    @PostMapping("/register")
    public JwtToken register(@RequestBody UsernameAndPassword usernameAndPassword) throws Exception {
        log.info("register user with param:[{}]", JSON.toJSONString(usernameAndPassword));
        return new JwtToken(ljwtService.registerUserAndGenerateToken(usernameAndPassword));
    }
}

Implementation of authentication coding

Here, we put authentication in the public module. Why? Here, we need to use authentication services not only in the authentication center, but also in other services. With the idea of encapsulation, we extract public methods and put them in Common

Create JWT Token parsing class TokenParseUtil

/**
 * @author : Leng Huanyuan
 * @date : 2021/12/5
 * @context: JWT Token Parsing tool class
 * @params :  null
 * @return :  * @return : null
 */
public class TokenParseUtil {

    public static LoginUserinfo parseUserInfoFromToken(String token) throws Exception {
        if (null == token) {
            return null;
        }
        Jws<Claims> claimsJws = parseToken(token, getPublicKey());
        Claims body = claimsJws.getBody();
        //If the Token has expired, null is returned
        if (body.getExpiration().before(Calendar.getInstance().getTime())) {
            return null;
        }
        //     Returns the user information saved in the Token
        return JSON.parseObject(
                body.get(CommonCanstant.JWT_USER_INFO_KEY).toString(), LoginUserinfo.class
        );
    }

    /*
     * Resolve JWT Token through public key
     * */
    private static Jws<Claims> parseToken(String token, PublicKey publicKey) {
        // Use the set signature public key to parse the claims information token
        return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
    }

    /*
     * Get getPublicKey according to the locally stored public key
     * */
    public static PublicKey getPublicKey() throws Exception {

        //Decoder we set the decoder to put the public key in
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(
                new BASE64Decoder().decodeBuffer(CommonCanstant.PUBLIC_KEY)
        );
        //Create an RSA instance and generate a public key object through the example
        return KeyFactory.getInstance("RSA").generatePublic(keySpec);
    }
}

Here is a problem. If the token is not a jwt token object, it will run out of exception without revealing the truth,

In fact, the problem here is not tenable. It should be that you have not passed in the token object. It is correct for us to throw an exception here, and it will not affect other services. Then, we can realize exception restart with sentinel and brother Hao pig. Here, we will not write a bottom-up method to analyze jwt token.

Authentication authorization

We write a test class to test whether the authorization and authentication objects are valid

/**
 * @author : Leng Huanyuan
 * @date : 2021/12/5
 * @context: JWT Related test classes
 * @params :  null
 * @return :  * @return : null
 */
@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class JWTServiceTest {

    @Autowired
    private IJWTService ijwtService;

    @Test
    public void testGenerateAndParseToken() throws Exception {
        String jwtToken = ijwtService.generateToken(
                "hyc@qq.com", "e10adc3949ba59abbe56e057f20f883e"
        );
        log.info("jwt token is:[{}]", jwtToken);
        LoginUserinfo userinfo = TokenParseUtil.parseUserInfoFromToken(jwtToken);
        log.info("userinfo by jwt prase token :[{}]", JSON.toJSONString(userinfo));
    }
}

Start the test and view the results

eyJhbGciOiJSUzI1NiJ9

.eyJlLWNvbW1lcmNlLXVzZXIiOiJ7XCJpZFwiOjExLFwidXNlcm5hbWVcIjpcImh5Y0BxcS5jb21cIn0iLCJqdGkiOiIzNDgwNjdjMi00MTBlLTQ3MjItYmM3ZS02NWQyYmNmYTRkN2MiLCJleHAiOjE2Mzg3MjAwMDB9

.ZbFl81MkIipJSULZLf4F2X2Fb0q1TwhHIMT7nyZsZVwUxXyZnK54RlzoGM_b-kMUdKO_Tab-qEeOT6Jn--FiKmbOziWXiBx3a-k5ipthMJx0Fez-X8Acty-Pg7zukNalugiLxGb5ophQoVQWRTDmv2hytGHqiV71HVyErznkJa36QQr6QsjXqlJleo3BBt-6BFzdTFPLUmdTEJ4XsmZBa_acUDGBhY0_tU2gYtKBWhwvMCknuyCcV-_GVI5EvgMIKRpeFSZrWfTsDG2y1MFcyzjKE6jnzek-YwT3XkzQ8eGzUbiOlaU_Zx5OJah-UtrKwqlAw9WbO71pNgEBefdsYw

As like as two peas, the JWT Token is packaged here. We can see that three points separately split header and payload and the signature is exactly the same as the structure we mentioned before.

userinfo by jwt prase token :[{"id":11,"username":"hyc@qq.com"}]

Get the objects we need to pass in jwt

Verify whether the interface provided externally is easy to use

Here we write an http script to test whether the interface provided by the external topic is useful

Token method

### Get Token -- implementation of login function
POST http://127.0.0.1:7000/ecommerce-authority-center/authority/token
Content-Type: application/json

{
  "username": "hyc@qq.com",
  "password": "e10adc3949ba59abbe56e057f20f883e"
}
POST http://127.0.0.1:7000/ecommerce-authority-center/authority/token

HTTP/1.1 200 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sun, 05 Dec 2021 15:35:52 GMT
Keep-Alive: timeout=60
Connection: keep-alive

{
  "token": "eyJhbGciOiJSUzI1NiJ9.eyJlLWNvbW1lcmNlLXVzZXIiOiJ7XCJpZFwiOjExLFwidXNlcm5hbWVcIjpcImh5Y0BxcS5jb21cIn0iLCJqdGkiOiIxNDU1M2FjZi1lZmE5LTQ4OTgtOTliYS1hNzA4NWI4MjU4MzAiLCJleHAiOjE2Mzg3MjAwMDB9.AlOpo6uf97R20ZLojXeun-3MK8DpSYlWxEygvDrtQeWaM9R0iKx-iW1VXnK6WoEntvqPxIrmPA7khjl3dXPa8kQHtdq-LVO7BDuZZDiQyZ64ZS7A9jWZr5JReSWBUSR1YUnsOvBRMkx4JVcAF3_W7nHwd722FFzOZRCr72hLHQIKpsugKtqjMEtaiEW0vcqphCYRJTAO_rQx1Lb1eVVg_Ufur0qSlKkV5dSJ0x3x9mc9UZRckwN0rrP7wQxZcrxJvKTfX7CkRRSO-CxZbG4WLokSaMtaGBMWU-7KGq7HSCZ0yuOgbbLdouHncsp6VD2tNLFdWSdJ_whCIbZxfX8R7w"
}

token obtained successfully

Here he was not responded to the package, which proves that our previous selection shielding annotation has also taken effect, which is in line with our expectations

Verify if the record data table does not return null

### Get Token -- implementation of login function
POST http://127.0.0.1:7000/ecommerce-authority-center/authority/token
Content-Type: application/json
### Casually written id

{
"username": "hyc1111@qq.com",
"password": "e10adc3949ba59abbe56e057f20f883e"
}

The returned result also meets our expectation and is null

POST http://127.0.0.1:7000/ecommerce-authority-center/authority/token

HTTP/1.1 200 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sun, 05 Dec 2021 15:40:44 GMT
Keep-Alive: timeout=60
Connection: keep-alive

{
  "token": null
}

register

### Register user and return Token -- implementation of registration function
POST http://127.0.0.1:7000/ecommerce-authority-center/authority/register
Content-Type: application/json

{
  "username": "hyc@qq.com",
  "password": "e10adc3949ba59abbe56e057f20f883e"
}

This user has been registered before. Let's see if the expected processing will be returned

POST http://127.0.0.1:7000/ecommerce-authority-center/authority/register

HTTP/1.1 200 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sun, 05 Dec 2021 15:42:00 GMT
Keep-Alive: timeout=60
Connection: keep-alive

{
  "token": null
}

Now let's register a new user

### Register user and return Token -- implementation of registration function
POST http://127.0.0.1:7000/ecommerce-authority-center/authority/register
Content-Type: application/json

{
  "username": "hyc11@qq.com",
  "password": "e10adc3949ba59abbe56e057f20f883e"
}

It meets the expected results and creates our expected objects. At this time, let's take a look at the data table

POST http://127.0.0.1:7000/ecommerce-authority-center/authority/register

HTTP/1.1 200 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sun, 05 Dec 2021 15:42:57 GMT
Keep-Alive: timeout=60
Connection: keep-alive

{
  "token": "eyJhbGciOiJSUzI1NiJ9.eyJlLWNvbW1lcmNlLXVzZXIiOiJ7XCJpZFwiOjEyLFwidXNlcm5hbWVcIjpcImh5YzExQHFxLmNvbVwifSIsImp0aSI6IjMxNDc0NmIwLTMyOGYtNDZkNS05ZTIwLTg3YjI0OWY1ZjZkOCIsImV4cCI6MTYzODcyMDAwMH0.MKxk-Q4BG5kaYFAsLiy13trtk_gDFmCKORpdE4EAwgSVecXFQcYfT1VvqSAKvoQLFsSlQAxOR5elV8CFOoKwAomwqdyyghZp63NKJ2smRbg3Y-4jWBzFVsUgcjOY2fwh7oNTdHEsWmLBYAh5r0hm_MysZsUEsE-cwb3sw8NSMk1OZp0J6tcRras7V1Uw5xXH8OnCoq2cUfdynJMHS29EzJT1TFPb8unVQ_A1RWodsHdK3n1Bl4wFbJjMtnHx7vzOeAUSNJx1XpAGdo0xYHK6HBpS9E1KBS3x1AnYFONM0DKd4-_QxMkBW1kkg2uWrRpf3GYZF20FKxXgmBAPHGZhew"
}

Object generation, function verification, everything is normal

Summary of authentication service center

Compare Token based and server based authentication

Tradition:

  • In the most traditional way, the Session id is used by the client to store cookie s, and the Session id is stored by the server
  • Session means that every time a user passes the authentication, the server needs to create a record to save the user information, which is usually in memory (or in redis). As more and more users pass the authentication, the cost of the server here will increase
  • When switching between different domain names, the request may be prohibited, that is, cross the problem

Based on token

  • The difference between JWT and Session is that they both store user information. However, the Session is on the server side, while JWT is on the client side
  • The JWT method disperses the user state to the client, which can significantly reduce the memory pressure of the server. The server only needs to parse the client's token with an algorithm to get the information

Comparison of the advantages and disadvantages of the two

  • Parsing method: JWT directly parses the user information using the algorithm; Session requires additional data mapping. Realize matching
  • Management method: JWT only has the limit of expiration time, and the Session data is saved in the server, which is more controllable
  • Cross platform: JWT is a string that can be propagated arbitrarily. The cross platform of Session requires a unified parsing platform, which is cumbersome
  • Timeliness: once JWT is generated and exists independently, it is difficult to achieve special control; Session timeliness has the final say of the logic of the server side.

TIPS: each has its own advantages and disadvantages. They are both login and authorization solutions

Topics: Back-end Spring Cloud Microservices