Chapter 5 - password encryption and microservice authentication JWT

Chapter 5 - password encryption and microservice authentication JWT

Learning objectives:

  • Complete user registration and send messages to RabbitMQ
  • Complete SMS microservice, be able to receive messages and call Alibaba cloud communication to complete SMS sending
  • Be able to use BCrypt encryption algorithm to achieve registration and login functions
  • Be able to name common authentication mechanisms
  • Be able to say the components of JWT and the advantages of using JWT
  • Ability to create and parse token s using JJWT
  • Can use JJWT to delete user authentication
  • Interceptor review

1 user microservice - user registration

1.1 demand analysis

Register account number, register with mobile number, send SMS verification code after filling in, fill in SMS verification code correctly to register successfully.

[failed to transfer the pictures in the external chain. The source station may have anti-theft chain mechanism. It is recommended to save the pictures and upload them directly (img-KMVb6yjL-1591408806143)(image-9.jpg))

What we do here is actually the message producer.

1.2 code generation

(1) Using code generator to generate user microservice code tensquare_user

(2) Copy to the current project and import in the parent project.

(3) Change the Application class name to UserApplication

(4) Modification application.yml The port in is 9008 and the URL is

jdbc:mysql://192.168.66.128:3306/tensquare_user?characterEncoding=UTF8

(5) Conduct Browser Test

1.3 send SMS verification code

Implementation idea: write API in user microservice, generate mobile phone verification code, store it in Redis and send it to RabbitMQ

1.3.1 preparations

(1) Because caching and message queuing are used, the user microservice_ User) introduces the starting dependency of redis and amqp.

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
		<dependency>  
			<groupId>org.springframework.boot</groupId>  
			<artifactId>spring-boot-starter-amqp</artifactId>  
		</dependency>

(2) Modification application.yml , add configuration under spring node

  redis:
    host: 192.168.66.128
  rabbitmq:
    host: 192.168.66.128

1.3.2 code implementation

(1) New method in UserService to send SMS verification code

	@Autowired
	private RedisTemplate redisTemplate;

	@Autowired
	private RabbitTemplate rabbitTemplate;

	/**
	 * Send SMS verification code
	 * @param mobile cell-phone number
	 */
	public void sendSms(String mobile){
		//1. Generate 6-digit SMS verification code
		Random random=new Random();
		int max=999999;//Maximum number
		int min=100000;//Minimum
		int code = random.nextInt(max);//Random generation
		if(code<min){
			code=code+min;
		}
		System.out.println(mobile+"The received verification code is:"+code);
		//2. Put the verification code into redis
		redisTemplate.opsForValue().set("smscode_"+mobile, code+"" ,5, TimeUnit.MINUTES );//Five minute expiration

		//3. Launch the verification code and mobile number into rabbitMQ
		Map<String,String> map=new HashMap();
		map.put("mobile",mobile);
		map.put("code",code+"");
		rabbitTemplate.convertAndSend("sms",map);
	}

(2) New method of UserController

	/**
	 * Send SMS verification code
	 * @param mobile
	 */
	@RequestMapping(value="/sendsms/{mobile}",method=RequestMethod.POST)
	public Result sendsms(@PathVariable String mobile ){
		userService.sendSms(mobile);
		return new Result(true,StatusCode.OK,"Sent successfully");
	}

(3) Start the microservice, create a queue named sms in rabbitMQ, and test the API

1.4 user registration

(1) User service adding method

	/**
	 * increase
	 * @param user user
	 * @param code Verification code filled in by the user
	 */
	public void add(User user,String code) {
		//Determine whether the verification code is correct
		String syscode = (String)redisTemplate.opsForValue().get("smscode_" + user.getMobile()); //Extract the correct verification code of the system
		if(syscode==null){
			throw new RuntimeException("Please click to get SMS verification code");
		}
		if(!syscode.equals(code)){
			throw new RuntimeException("Incorrect verification code input");
		}

		user.setId( idWorker.nextId()+"" );
		user.setFollowcount(0);//Number of concerns
		user.setFanscount(0);//Number of fans
		user.setOnline(0L);//Online duration
		user.setRegdate(new Date());//Registration date
		user.setUpdatedate(new Date());//Update date
		user.setLastdate(new Date());//Last landing date

		userDao.save(user);
	}

(2) How to add UserController

	/**
	 * User registration
	 * @param user
	 */
	@RequestMapping(value="/register/{code}",method=RequestMethod.POST)
	public Result register( @RequestBody User user  ,@PathVariable String code){
		userService.add(user,code);
		return new Result(true,StatusCode.OK,"login was successful");
	}

(3) Testing

2 SMS micro service

2.1 demand analysis

Develop SMS sending microservice, extract messages from rabbitMQ, and call Alibaba greater than SMS interface to realize SMS sending. (about SMS Alibaba greater than, we have explained it in the previous e-commerce project, so account application and other links have been omitted)

What we are actually doing here is consumers of information

2.2 extract messages in the queue

2.2.1 project construction

(1) Create engineering module: tensquare_sms, pom.xml Introduce dependency

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

(2) Create application.yml

server: 
  port: 9009
spring: 
  application:  
    name: tensquare-sms #Specify service name
  rabbitmq: 
    host: 192.168.66.128

(3) com.tensquare.sms Create startup class under package

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

2.2.2 message listening class

(1) Create SMS monitoring class to obtain mobile number and verification code

/**
 * SMS monitoring class
 */
@Component
@RabbitListener(queues = "sms")
public class SmsListener {

    /**
     *  Send SMS
     * @param message
     */
    @RabbitHandler
    public void sendSms(Map<String,String> message){
        System.out.println("cell-phone number:"+message.get("mobile"));
        System.out.println("Verification Code:"+message.get("code"));
    }

}

(2) Run the smsaapplication class, and the console displays the mobile number and verification code

2.3 sending SMS (Alibaba cloud communication)

2.3.1 Alibaba cloud communication introduction

Alicloud communication (original name - aligreater than) is Alibaba cloud's products integrate the communication capabilities of the three major operators. By combining traditional communication services and capabilities with the Internet and innovatively integrating Alibaba's ecological content, Alibaba strives to provide high-quality services for small and medium-sized enterprises and developers rather than personalized services such as SMS, voice, direct charging of traffic, private special line, store mobile phone number, etc. Through the communication capacity of Alibaba greater than connecting the three major operators, fully integrate Alibaba ecology, provide communication and data services to developers in the way of opening API and SDK, and better support enterprise business development and innovation services.

2.3.2 preparations

(1) On Alibaba cloud official website www.alidayu.com Registered account

(2) Download "Alibaba cloud" APP by mobile phone and complete real name authentication

(3) Log in to alicloud and choose "SMS service" from products“

(4) Application signature

(5) Application template

(6) Create accessKey (keep it secret!)

(7) Recharge (there's no need to charge too much, 1-2 yuan is enough, local tyrants please feel free ~)

2.3.3 coding

(1) In the engineering module tensquare_sms, pom.xml Introduce dependency

	<dependency>
   		<groupId>com.aliyun</groupId>
   		<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
   		<version>1.1.0</version>
   	</dependency>
   	<dependency> 
   		<groupId>com.aliyun</groupId>
   		<artifactId>aliyun-java-sdk-core</artifactId>
   		<version>3.2.5</version>
   	</dependency>  

(2) Modification application.yml , add configuration

aliyun: 
  sms: 
    accessKeyId: LTAIKwFq9lPHwLvh
    accessKeySecret: I would not tell you.
    template_code: SMS_149385475
    sign_name: Spreading wisdom Podcast

(3) Create SMS tool class smutil (the resource has been provided and can be copied directly)

package com.tensquare.sms;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsResponse;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
/**
 * SMS tools
 * @author Administrator
 *
 */
@Component
public class SmsUtil {

    //Product Name: cloud communication short message API product, developers do not need to replace it
    static final String product = "Dysmsapi";
    //Product domain name, developers do not need to replace
    static final String domain = "dysmsapi.aliyuncs.com";
    
    @Autowired
    private Environment env;

    // TODO here needs to be replaced by the developer's own AK (found on Alibaba cloud access console)
    
    /**
     * Send SMS
     * @param mobile cell-phone number
     * @param template_code Template No
     * @param sign_name autograph
     * @param param parameter
     * @return
     * @throws ClientException
     */
    public SendSmsResponse sendSms(String mobile,String template_code,String sign_name,String param) throws ClientException {
    	String accessKeyId =env.getProperty("aliyun.sms.accessKeyId");
    String accessKeySecret = env.getProperty("aliyun.sms.accessKeySecret");
        //Self adjustable timeout
        System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
        System.setProperty("sun.net.client.defaultReadTimeout", "10000");
        //Initialization of acsClient, region is not supported temporarily
        IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
        DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
        IAcsClient acsClient = new DefaultAcsClient(profile);
        //Assembly request object - for details, please refer to the console - Documentation section
        SendSmsRequest request = new SendSmsRequest();
        //Required: mobile number to be sent
        request.setPhoneNumbers(mobile);
        //Required: SMS signature - can be found in SMS console
        request.setSignName(sign_name);
        //Required: SMS template - can be found in SMS console
        request.setTemplateCode(template_code);
        //Optional: the variables in the template replace the JSON string. For example, when the template content is "Dear ${name}, and your verification code is ${code}", the value here is
        request.setTemplateParam(param);
        //Optional - uplink SMS extension code (please ignore this field for users without special requirements)
        //request.setSmsUpExtendCode("90997");
        //Optional: outId is the extension field provided to the business party, which will be brought back to the caller in the SMS receipt message
        request.setOutId("yourOutId");
        //hint an exception may be thrown here. Pay attention to catch
        SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
        return sendSmsResponse;
    }

    public  QuerySendDetailsResponse querySendDetails(String mobile,String bizId) throws ClientException {
    	String accessKeyId =env.getProperty("accessKeyId");
        String accessKeySecret = env.getProperty("accessKeySecret");
        //Self adjustable timeout
        System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
        System.setProperty("sun.net.client.defaultReadTimeout", "10000");
        //Initialization of acsClient, region is not supported temporarily
        IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
        DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
        IAcsClient acsClient = new DefaultAcsClient(profile);
        //Assemble request object
        QuerySendDetailsRequest request = new QuerySendDetailsRequest();
        //Required - number
        request.setPhoneNumber(mobile);
        //Optional - serial number
        request.setBizId(bizId);
        //Required - send date supports query within 30 days, format yyyyMMdd
        SimpleDateFormat ft = new SimpleDateFormat("yyyyMMdd");
        request.setSendDate(ft.format(new Date()));
        //Required - page size
        request.setPageSize(10L);
        //Required - the current page number is counted from 1
        request.setCurrentPage(1L);
        //hint an exception may be thrown here. Pay attention to catch
        QuerySendDetailsResponse querySendDetailsResponse = acsClient.getAcsResponse(request);
        return querySendDetailsResponse;
    }
}

(4) Modify the message monitoring class to complete the SMS sending

/**
 * SMS monitoring class
 */
@Component
@RabbitListener(queues = "sms")
public class SmsListener {

    @Autowired
    private  SmsUtil smsUtil;

    @Value("${aliyun.sms.template_code}")
    private String templateCode;//Template No

    @Value("${aliyun.sms.sign_name}")
    private String signName;//autograph


    @RabbitHandler
    public void sendSms(Map<String,String> map){
        System.out.println("cell-phone number:"+map.get("mobile"));
        System.out.println("Verification Code:"+map.get("code"));
        try {
            smsUtil.sendSms(map.get("mobile"),templateCode,signName,"{\"code\":"+ map.get("code") +"}");
        } catch (ClientException e) {
            e.printStackTrace();
        }
    }

}

3 BCrypt password encryption

3.1 preparations

Any application must not save the password in clear text for security. The password should be encrypted using a hash algorithm. There are many standard algorithms such as SHA or MD5. It is a good choice to combine salt. Spring Security provides BCryptPasswordEncoder class to implement spring's PasswordEncoder interface to encrypt passwords using BCrypt strong hash method.

The result of BCrypt strong hash method is different every time it is encrypted.

(1)tensquare_ pom import dependency of user Engineering

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

(2) Add configuration class (provided in resource / tool class)

After we add the spring security dependency, all the addresses are controlled by spring security. At present, we only need to use BCrypt password encryption, so we need to add a configuration class to configure that all addresses can be accessed anonymously.

/**
 * Security configuration class
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/**").permitAll()
                .anyRequest().authenticated()
                .and().csrf().disable();
    }
}

(3) Modify tensquare_ Application of user project, configuration bean

	@Bean
	public BCryptPasswordEncoder bcryptPasswordEncoder(){
		return new BCryptPasswordEncoder();
	}

3.2 administrator password encryption

3.2.1 add administrator password encryption

Modify tensquare_ AdminService of user project

	@Autowired
	BCryptPasswordEncoder encoder;
	
	public void add(Admin admin) {
		admin.setId(idWorker.nextId()+""); //Primary key value
		//Password encryption
		String newpassword = encoder.encode(admin.getPassword());//Encrypted password
		admin.setPassword(newpassword);			
		adminDao.save(admin);
	}

3.2.2 administrator login password verification

(1) AdminDao add method definition

    public Admin findByLoginname(String loginname);

(2) AdminService adding method

	/**
	 * Search by login name and password
	 * @param loginname
	 * @param password
	 * @return
	 */
	public Admin findByLoginnameAndPassword(String loginname, String password){
		Admin admin = adminDao.findByLoginname(loginname);
		if( admin!=null && encoder.matches(password,admin.getPassword())){
			return admin;
		}else{
			return null;
		}
	}

(3) AdminController adding method

	/**
	 * User login
	 * @param loginname
	 * @param password
	 * @return
	 */
	@RequestMapping(value="/login",method=RequestMethod.POST)
	public Result login(@RequestBody Map<String,String> loginMap){
		Admin admin = adminService.findByLoginnameAndPassword(loginMap.get("loginname"), loginMap.get("password"));
		if(admin!=null){
			return new Result(true,StatusCode.OK,"Login succeeded");
		}else{
			return new Result(false,StatusCode.LOGINERROR,"Wrong user name or password");
		}
	}

3.3 user password encryption

3.3.1 add user password encryption

(4) Modify tensquare_user engineering's UserService class, introducing BCryptPasswordEncoder

	@Autowired
	BCryptPasswordEncoder encoder;

(5) Modify tensquare_ Add method of UserService class of user project, add password encryption logic

/**
	 * increase
	 * @param user
	 * @param code
	 */
	public void add(User user,String code) {
		........
		........
		........
		//Password encryption
		String newpassword = encoder.encode(user.getPassword());//Encrypted password
		user.setPassword(newpassword);
		userDao.save(user);
	}

(4) After the test runs, add data

{
    "mobile": "13901238899"
    "password": "123123",
}

The password in the database is in the following form

$2a$10$a/EYRjdKwQ6zjr0/HJ6RR.rcA1dwv1ys7Uso1xShUaBWlIWTyJl5S

3.3.2 user login password judgment

(1) Modify tensquare_ UserDao interface of user project, adding method definition

     /**
     * Query users according to mobile phone number
     * @param mobile
     * @return
     */
    public User findByMobile(String mobile);

(2) Modify tensquare_ UserService class of user project, adding method

	/**
	 * Query users according to mobile phone number and password
	 * @param mobile
	 * @param password
	 * @return
	 */
	public User findByMobileAndPassword(String mobile,String password){
		User user = userDao.findByMobile(mobile);
		if(user!=null &&  encoder.matches(password,user.getPassword())){
			return user;
		}else{
			return null;
		}
	}

(4) Modify tensquare_ Add login method to UserController class of user project

	/**
	 * User login
	 * @param mobile
	 * @param password
	 * @return
	 */
	@RequestMapping(value="/login",method=RequestMethod.POST)
	public Result login(@RequestBody Map<String,String> loginMap){
         User user =                                    userService.findByMobileAndPassword(loginMap.get("mobile"),loginMap.get("password"));
		if(user!=null){
			return new Result(true,StatusCode.OK,"Login succeeded");
		}else{
			return new Result(false,StatusCode.LOGINERROR,"Wrong user name or password");
		}
	}

(4) Use the newly added account to test and view the returned results

4 common authentication mechanisms

4.1 HTTP Basic Auth

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

4.2 Cookie Auth

Cookie authentication mechanism is to create a session object on the server side for a request authentication, and a cookie object on the browser side of the client side at the same time. The cookie object brought by the client side matches the session object on the server side to realize state management. By default, cookies are deleted when we close the browser. However, the cookie can be valid for a certain period of time by modifying its expire time;

[failed to transfer the pictures in the external link. The source station may have anti-theft chain mechanism. It is recommended to save the pictures and upload them directly (img-9CGpeRkO-1591408806146)(image-1.jpg))

4.3 OAuth

OAuth (open authorization) is an open authorization standard, which allows the user to let the third-party application access the user's private resources (such as photos, videos, contact lists) stored on a web service without providing the user name and password to the third-party application.

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

The following is the process of OAuth2.0:

[failed to save the image in the external link. The source station may have anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-23yabAsO-1591408806148)(image-2.jpg))

This authentication mechanism based on OAuth is suitable for personal consumer Internet products, such as social APP and other applications, but it is not suitable for enterprise applications with their own authentication authority management.

4.4 Token Auth

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

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

[failed to transfer the pictures in the external link. The source station may have anti-theft chain mechanism. It is recommended to save the pictures and upload them directly (img-sDuG7Zcn-1591408806151)(image-3.jpg))

Advantages of Token Auth

What are the advantages of Token mechanism over Cookie mechanism?

  • Support cross domain access: cookies are not allowed to cross domain access, which does not exist for Token mechanism, provided that the transmitted user authentication information is transmitted through HTTP header
  • Stateless (also known as server extensible line): Token mechanism does not need to store session information in the server, because Token itself contains the information of all logged in users, and only needs to store state information in the client's cookie or local media
  • More suitable for CDN: you can request all the data of your server (such as javascript, HTML, pictures, etc.) through the content distribution network, and your server only needs to provide the API
  • Decoupling: no need to bind to a specific authentication scheme. Token can be generated anywhere, as long as you can make token generation call when your API is called
  • More suitable for mobile applications: when your client is a native platform (iOS, Android, Windows 8, etc.), cookies are not supported (you need to process through the Cookie container), so it is much easier to use Token authentication mechanism.
  • CSRF: because you no longer rely on cookies, you don't need to consider the prevention of CSRF (Cross Site Request Forgery).
  • Performance: a round trip time of network (query session information through database) is always more time-consuming than Token verification and resolution of hmacha256 calculation
  • There is no need to do special processing for the login page: if you use Protractor to do function test, there is no need to do special processing for the login page
  • Based on Standardization: your API can use the standardized JSON Web Token (JWT). This standard already has multiple back-end libraries (. NET, Ruby, Java,Python, PHP) and support from multiple companies (such as Firebase,Google, Microsoft)

5 implementation of Token authentication mechanism based on JWT

5.1 what is JWT

JSON Web Token (JWT) is a very lightweight specification. This specification allows us to use JWT to pass safe and reliable information between users and servers. Token = = "token

5.2 JWT composition

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

Head

The header is used to describe the most basic information about the JWT, such as its type, algorithm used for signature, etc. This can also be represented as a JSON object.

{"typ":"JWT","alg":"HS256"}

In the header, it indicates that the signature algorithm is HS256 algorithm. We code BASE64

http://tool.oschina.net/encrypt?type=3

http://base64.xpcha.com/,

The encoded string is as follows:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

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

Load (payload)

The load is where the payload is stored. The name seems to refer to the goods carried on the aircraft. The valid information consists of three parts

(1) Declaration registered in the standard (recommended but not mandatory)

iss: jwt issuer
 Sub: users JWT is targeting
 aud: the party receiving jwt
 Exp: the expiration time of JWT, which must be greater than the issuing time
 nbf: defines when the jwt will not be available
 IAT: issuing time of JWT
 JTI: the unique identity of JWT, which is mainly used as a one-time token to avoid replay attack. User ID

(2) Public statement

Public declaration can add any information, generally related information of users or other necessary information required by business. However, it is not recommended to add sensitive information, because this part can be decrypted in the client

(3) Private declaration (custom)

Private statement is a statement defined by both the provider and the consumer. It is generally not recommended to store sensitive information, because base64 is symmetric decrypted, which means that this part of information can be classified as clear text information.

This refers to the custom claim. For example, the admin and name in the previous structure example belong to the custom claim. The difference between these claims and those stipulated in JWT standard is that: after receiving JWT, the JWT receiver knows how to verify the claims of these standards (whether it can be verified or not); while private claims will not be verified unless the receiver is explicitly told to verify these claims and the rules.

Define a payload:

{"sub":"1234567890","name":"John Doe","admin":true}

Then it is base64 encoded to get the second part of Jwt.

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

signature

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

Header (after Base64)

Payload (after Base64)

Secret: the secret key is customized (you can't tell others if you kill it)

In this part, the base64 encoded header and the base64 encoded payload need to use the string composed of. Connection, and then use the declared encryption method in the header to add salt secret combination encryption, and then constitute the third part of jwt.

TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

Connect these three parts into a complete string to form the final jwt:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

Note: secret is saved on the server side, and the signing generation of jwt is also on the server side. Secret is used to sign jwt and verify jwt. Therefore, it is your server's private key, which should not be revealed in any scenario. Once the client knows the secret, it means that the client can sign and issue jwt itself.

JJWT implementation of Java

6.1 what is JJWT

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

6.2 JJWT quick start

6.2.1 token creation

(1) Create maven project and introduce dependency

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.6.0</version>
        </dependency>

(2) Create class CreateJwtTest to generate token

public class CreateJwtTest {

    public static void main(String[] args) {

        JwtBuilder builder= Jwts.builder().setId("888")
                .setSubject("Xiaobai")
                .setIssuedAt(new Date())
                .signWith(SignatureAlgorithm.HS256,"itcast");
        System.out.println( builder.compact() );

    }
}

setIssuedAt is used to set the issuing time

signWith is used to set the signature key

(3) Test run, the output is as follows:

eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1MjM0MTM0NTh9.gq0J-cOM_qCNqU_s-d_IrRytaNenesPmqAIhQpYXHZk

Running again, you will find that the result of each run is different, because our load contains time.

6.2.2 token analysis

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

Create ParseJwtTest

public class ParseJwtTest {

    public static void main(String[] args) {
        String token="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1MjM0MTM0NTh9.gq0J-cOM_qCNqU_s-d_IrRytaNenesPmqAIhQpYXHZk";
        Claims claims = Jwts.parser().setSigningKey("itcast").parseClaimsJws(token).getBody();
        System.out.println("id:"+claims.getId());
        System.out.println("subject:"+claims.getSubject());
        System.out.println("IssuedAt:"+claims.getIssuedAt());
    }
}

Try to tamper with the token or signature key, and it will be found that an error will be reported at runtime, so resolving the token is to verify the token

6.2.3 token expiration verification

There are many times when we don't want the issued token to be permanent, so we can add an expiration time for the token.

Create CreateJwtTest2

public class CreateJwtTest2 {

    public static void main(String[] args) {

        //To facilitate testing, we set the expiration time to 1 minute
        long now = System.currentTimeMillis();//current time 
        long exp = now + 1000*60;//Expires in 1 minute
        JwtBuilder builder= Jwts.builder().setId("888")
                .setSubject("Xiaobai")
                .setIssuedAt(new Date())
                .signWith(SignatureAlgorithm.HS256,"itcast")
          		.setExpiration(new Date(exp));
        System.out.println( builder.compact() );
    }
}

The setExpiration method is used to set the expiration time

Modify ParseJwtTest

public class ParseJwtTest {

    public static void main(String[] args) {
        String compactJws="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1MjM0MTY1NjksImV4cCI6MTUyMzQxNjYyOX0.Tk91b6mvyjpKcldkic8DgXz0zsPFFnRgTgkgcAsa9cc";
        Claims claims = Jwts.parser().setSigningKey("itcast").parseClaimsJws(compactJws).getBody();
        System.out.println("id:"+claims.getId());
        System.out.println("subject:"+claims.getSubject());
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

        System.out.println("Time filed :"+sdf.format(claims.getIssuedAt()));
        System.out.println("Expiration time:"+sdf.format(claims.getExpiration()));
        System.out.println("current time :"+sdf.format(new Date()) );

    }
}

Test run. It can be read normally when it is not expired. It will be raised when it is expired io.jsonwebtoken.ExpiredJwtException Exception.

Exception in thread "main" io.jsonwebtoken.ExpiredJwtException: JWT expired at 2018-06-08T21:44:55+0800. Current time: 2018-06-08T21:44:56+0800
	at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:365)
	at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:458)
	at io.jsonwebtoken.impl.DefaultJwtParser.parseClaimsJws(DefaultJwtParser.java:518)
	at cn.itcast.demo.ParseJwtTest.main(ParseJwtTest.java:13)

6.2.4 custom claims

Our example just stored the id and subject information. If you want to store more information (such as roles), you can define custom claims and create CreateJwtTest3

public class CreateJwtTest3 {

    public static void main(String[] args) {

        //To facilitate testing, we set the expiration time to 1 minute
        long now = System.currentTimeMillis();//current time 
        long exp = now + 1000*60;//Expires in 1 minute
        JwtBuilder builder= Jwts.builder().setId("888")
                .setSubject("Xiaobai")
                .setIssuedAt(new Date())
                .signWith(SignatureAlgorithm.HS256,"itcast")
                .setExpiration(new Date(exp))
                .claim("roles","admin")
                .claim("logo","logo.png");
        System.out.println( builder.compact() );
    }
}

Modify ParseJwtTest

public class ParseJwtTest {

    public static void main(String[] args) {
        String compactJws="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1MjM0MTczMjMsImV4cCI6MTUyMzQxNzM4Mywicm9sZXMiOiJhZG1pbiIsImxvZ28iOiJsb2dvLnBuZyJ9.b11p4g4rE94rqFhcfzdJTPCORikqP_1zJ1MP8KihYTQ";
        Claims claims = Jwts.parser().setSigningKey("itcast").parseClaimsJws(compactJws).getBody();
        System.out.println("id:"+claims.getId());
        System.out.println("subject:"+claims.getSubject());
        System.out.println("roles:"+claims.get("roles"));
        System.out.println("logo:"+claims.get("logo"));

        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

        System.out.println("Time filed :"+sdf.format(claims.getIssuedAt()));
        System.out.println("Expiration time:"+sdf.format(claims.getExpiration()));
        System.out.println("current time :"+sdf.format(new Date()) );

    }
}

7 authentication of the 10th power microservice

7.1 JWT tool class writing

(1)tensquare_common project introduces dependency (considering the universality of tool class)

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.6.0</version>
        </dependency>

(2) Modify tensquare_common project, creating util.JwtUtil

@ConfigurationProperties("jwt.config")
public class JwtUtil {

    private String key ;

    private long ttl ;//An hour

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public long getTtl() {
        return ttl;
    }

    public void setTtl(long ttl) {
        this.ttl = ttl;
    }

    /**
     * Generate JWT
     *
     * @param id
     * @param subject
     * @return
     */
    public String createJWT(String id, String subject, String roles) {
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        JwtBuilder builder = Jwts.builder().setId(id)
                .setSubject(subject)
                .setIssuedAt(now)
                .signWith(SignatureAlgorithm.HS256, key).claim("roles", roles);
        if (ttl > 0) {
            builder.setExpiration( new Date( nowMillis + ttl));
        }
        return builder.compact();
    }

    /**
     * Analyze JWT
     * @param jwtStr
     * @return
     */
    public Claims parseJWT(String jwtStr){
        return  Jwts.parser()
                .setSigningKey(key)
                .parseClaimsJws(jwtStr)
                .getBody();
    }

}

(3) Modify tensquare_user Engineering application.yml , add configuration

jwt:
 config:
    key: itcast
    ttl: 3600000

7.2 the administrator logs in the background to sign and issue the token

(1) Configuration bean. Modify tensquare_user engineering Application class

	@Bean
	public JwtUtil jwtUtil(){
		return new JwtUtil();
	}

(2) Modify the login method of AdminController

	@Autowired
	private JwtUtil jwtUtil;

	/**
	 * User login
	 * @param loginname
	 * @param password
	 * @return
	 */
	@RequestMapping(value="/login",method=RequestMethod.POST)
	public Result login(@RequestBody Map<String,String> loginMap){
		Admin admin = adminService.findByLoginnameAndPassword(loginMap.get("loginname"), loginMap.get("password"));
		if(admin!=null){
			//Generate token
			String token = jwtUtil.createJWT(admin.getId(), admin.getLoginname(), "admin");
			Map map=new HashMap();
			map.put("token",token);
			map.put("name",admin.getLoginname());//Login name
			return new Result(true,StatusCode.OK,"Login succeeded",map);
		}else{
			return new Result(false,StatusCode.LOGINERROR,"Wrong user name or password");
		}
	}

Test run results

{
  "flag": true,
  "code": 20000,
  "message": "Login succeeded",
  "data": {
    "token": "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI5ODQzMjc1MDc4ODI5MzgzNjgiLCJzdWIiOiJ4aWFvbWkiLCJpYXQiOjE1MjM1MjQxNTksInJvbGVzIjoiYWRtaW4iLCJleHAiOjE1MjM1MjQ1MTl9._YF3oftRNTbq9WCD8Jg1tqcez3cSWoQiDIxMuPmp73o",
    "name":"admin"
  }
}

7.3 delete user function authentication

Requirement: to delete a user, you must have administrator rights, otherwise you cannot delete it.

Front end and back end agreement: when the front end requests the microservice, it needs to add the Authorization of the header information, the content of which is Bearer + space + token

(1) Modify the delete method of UserController, judge the header information in the request, extract the token and verify the permission.

	@Autowired
	private HttpServletRequest request;

	/**
	 * delete
	 * @param id
	 */
	@RequestMapping(value="/{id}",method= RequestMethod.DELETE)
	public Result delete(@PathVariable String id ){

		String authHeader = request.getHeader("Authorization");//Get header information
		if(authHeader==null){
			return new Result(false,StatusCode.ACCESSERROR,"Insufficient authority");
		}
		if(!authHeader.startsWith("Bearer ")){
			return new Result(false,StatusCode.ACCESSERROR,"Insufficient authority");
		}
		String token=authHeader.substring(7);//Extract token
		Claims claims = jwtUtil.parseJWT(token);
		if(claims==null){
			return new Result(false,StatusCode.ACCESSERROR,"Insufficient authority");
		}
		if(!"admin".equals(claims.get("roles"))){
			return new Result(false,StatusCode.ACCESSERROR,"Insufficient authority");
		}

		userService.deleteById(id);
		return new Result(true,StatusCode.OK,"Delete succeeded");
	}

7.4 using interceptor to realize token authentication

If we write a piece of code for each method, and the redundancy is too high, which is not conducive to maintenance, then how to make our code look cleaner? We can put this code into the interceptor to implement

7.4.1 add interceptor

Spring provides us with org.springframework.web.servlet.handler.HandlerInterceptorAdapter This adapter, inheriting this class, can easily implement its own interceptor. He has three ways:

Respectively implement preprocessing, post-processing (call Service and return ModelAndView, but no page rendering), return processing (the page has been rendered)
In preHandle, you can code and control security;
In postHandle, there is an opportunity to modify ModelAndView;
In afterCompletion, you can log whether an exception has occurred according to whether ex is null.

(1) Create the interceptor class. establish com.tensquare.user.interceptor

@Component
public class JwtInterceptor extends HandlerInterceptorAdapter {

	@Autowired
	private JwtUtil jwtUtil;

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		System.out.println("After the interceptor");
		return true;
	}
}

(2) Configuring interceptor classes, creating com.tensquare.user.ApplicationConfig

@Configuration
public class ApplicationConfig extends WebMvcConfigurationSupport {

	@Autowired
	private JwtInterceptor jwtInterceptor;

	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(jwtInterceptor).
				addPathPatterns("/**").
				excludePathPatterns("/**/login");
	}
}

Summary of interview questions

How to send SMS in a project

Alibaba cloud communications (Alibaba greater than)

How do you implement microservice authentication?

JWT implementation of microservice authentication

Why JWT?

(1) Zero coupling between modules

(2) Efficient execution of authentication logic

(3) Can support more types of clients (front-end H5 Android IOS)

Tags: Mobile Java Spring RabbitMQ

Posted on Fri, 05 Jun 2020 22:23:53 -0400 by bqheath