1. User authentication analysis
The above flow chart describes the various micro-services that users need to operate. Users need to access customer micro-services to view their personal information, place an order, and buy goods in seconds. Each service needs to authenticate users. After the authentication is successful, users’ roles need to be identified and corresponding functions are authorized to access.
1.1 Authentication and Authorization
The identity authentication
- User identity authentication: When a user accesses system resources, the system authenticates the user’s identity to ensure that the user’s identity is valid. Common forms of user identity authentication include user name and password login and fingerprint punching. In general terms, it’s the same thingVerify that the user account password is correct.
The user authorization
- After a user is authenticated, the system checks whether the user has the permission to access resources.Only system resources that have permission can be accessed. Resources that do not have permission cannot be accessedThis process is called user authorization.
1.2 Single sign-on
- Among the items accessed by users, there are at least three microservices that need to identify user identities. It would be too troublesome for users to log in once when accessing each microservice. In order to improve user experience, we need to achieveAllows users to log in to one system and any other trusted system can access it, this function is calledSingle sign-on (sso).
- Single sign-on (SSO) cannot be the same as one account that supports only one device to log in online at the same time.
- Single sign-on (sso)Single Sign On, or SSO for short, is one of the more popular solutions for enterprise business integration. SSO is defined as having multiple applications,Users only need to log in once to access all trusted applications.
1.3 Login using a Third-party Account
With the platform opening strategy of domestic and foreign giants and the development of mobile Internet, third-party login is no longer a strange product design concept. The so-called third-party login refers to the function of quickly completing the login or registration of your application based on the user’s existing account and password on the third-party platform. The third-party platforms here are generally those with a large number of users, such as Facebook and Twitter in foreign countries and Weibo, wechat and QQ in China.
Advantages of third-party account login
- Compared with local registration, third-party login is generally more convenient and fast, which can significantly reduce the cost of registration and login and facilitate users to quickly log in or register.
- After the first successful binding, the user can then perform one-click logins, making subsequent logins much easier than in-app logins.
- For some applications, the use of third-party logins can meet their needs, so there is no need to design and develop their own account system.
- Through authorization, users can share their in-app activities on third-party platforms to promote themselves and increase product awareness.
- Through authorization, users can obtain social information such as friends or fans on third-party platforms, so as to carry out targeted marketing campaigns on users’ social networks, providing another channel for product marketing.
2. Certification technical scheme
2.1 Single sign-on technical solution
To realize single sign-on (SSO) in distributed systems, authentication systems are usually extracted independently and user identity information is stored in a separate storage medium, such as MySQL and Redis. Considering performance requirements, it is usually stored in Redis, as shown in the following figure:
Single sign-on features:
- The authentication system is an independent system.
- Each subsystem communicates with the authentication system through Http or other protocols to complete user authentication.
- User identity information is stored in the Redis cluster.
Single sign-on user authentication framework in Java:
- Apache Shiro
- CAS
- Spring security CAS
2.2 Oauth2 authentication
OAuth (Open Authorization) is an open standard that allows users to authorize third party mobile applications to access their information stored on another service provider without having to provide a user name and password to third party mobile applications or share all content of their data. OAuth2.0 is a continuation of the OAuth protocol.
2.2.1 Oauth2 Authentication process
The third party authentication technology solution is mainly to solve the general standard of authentication protocol, because to achieve cross-system authentication, each system must follow a certain interface protocol.
- Reference: baike.baidu.com/item/oAuth/…
- Request: tools.ietf.org/html/rfc674…
Below analysis of an Oauth2 authentication example, dark horse programmer website using wechat authentication process:
Oauth2 includes the following roles:
- 1. The client itself does not store resources, and requires the authorization of the resource owner to request resources from the resource server, such as Changgou Online Android client, Changgou online Web client (browser), wechat client, etc.
- 2. The owner of the resource is usually the user or the application, that is, the owner of the resource.
- 3. The authorization server (also called the authentication server) authenticates the identity of the resource and authorizes the access to the resource. To access resources, clients must be authorized by the resource owner through the authentication server.
- 4. Resource server A server that stores resources. For example, the Changgou user management server stores the user information of Changgou. The client ultimately accesses the resource server to obtain resource information.
2.3 Spring Security Oauth2 Authentication solution
Spring Security is a powerful and highly customizable authentication and access control framework. Spring Security framework integrates Oauth2 protocol. The following figure is the project certification framework diagram:
- 1. The user requests the authentication service to complete the authentication.
- 2. The authentication service issues the user identity token. Having the identity token means that the user’s identity is legitimate.
- 3. When a user requests resource services with a token, the request must pass through the gateway first.
- 4. The gateway verifies the validity of the user identity token. If the identity token is invalid, the user does not log in.
- 5. The resource service obtains the token and completes authorization according to the token.
- 6. The resource service responds to the resource information after completing authorization.
3. Oauth2 authorization mode
Oauth2 has the following authorization modes:
- 1. Authorization Code[common]
- 2. Implicit Authorization patterns
- 3. Resource Owner Password Credentials[common]
- 4. Client Credentials
3.1 Authorization Code Implementation of the authorization Mode
The above example of the dark horse programmer website using QQ authentication process is the authorization code mode, the process is as follows:
- 1. The client requests authorization from a third party
- 2. The user (resource owner) agrees to authorize the client
- 3. The client obtains the authorization code and requests the authentication server to apply for a token
- 4. The authentication server responds to the token to the client
- 5. The client requests resources from the resource server, and the resource service verifies the validity of the token and completes authorization
- 6. The resource server returns the protected resource
(1) Apply for an authorization code
Request authentication service to obtain authorization code:
# Get request:
http://localhost:9001/oauth/authorize?client_id=changgou&response_type=code&scop=app&redirect_uri=http://localhost
Copy the code
The parameter list is as follows:
Authorization configuration class AuthorizationServerConfig. Java has a complete code below!
parameter | Parameter is introduced |
---|---|
Client_id: | The client ID is the same as that specified in the authorization configuration class |
The payload: | The authorization code mode is fixed to Code |
Scop: | Client scope, consistent with scoP set in the authorization configuration class |
Redirect_uri: | Redirect URI. After the authorization code is applied successfully, it will redirect to this address, followed by the code parameter (authorization code). |
The browser accesses the above Get request and automatically redirects to the following page:
Enter your account and password and click Login. Upon receiving the request, Spring Security invokes the loadUserByUsername method of the UserDetailsService interface to query the correct password of the user. Enter the client ID of the current project as Changgou and the secret key as Changgou to pass the authentication.
Next, enter the authorization page:
Click Authorize, next return authorization code: authentication service carries authorization code jump redirect_URI,code=aiT1wn is the returned authorization code!
(2) Token application
Once you have the authorization code, you can apply for a token. Send a request:
# Post request
http://localhost:9001/oauth/token
Copy the code
The parameters are as follows:
parameter | Parameter is introduced |
---|---|
Grant_type: | Authorization type: Authorization_code indicates the authorization code mode |
Code: | The entitlement code is just obtained. Note that the entitlement code is invalid if you use it only once. You need to apply for it again. |
Redirect_uri: | The redirect URL must be the same as the redirect_URI used when applying for an authorization code |
Note: This request link requires HTTP Basic authentication.
What is HTTP Basic authentication? An authentication mode defined by the HTTP protocol. The client ID and password are concatenated in the format of “Client ID: Client password” and encoded in Base64. The client ID and password are placed in the header to request the server. Basic WGNXZWJBcHA6WGNXZWJBcHA= Wgnxzwjbcha = Is the Base64 encoding of the user name: password. The server returns 401 Unauthorized when authentication fails.
The above tests were completed using Postman, HTTP Basic authentication:
[Img – BCKESwXX-1611645795044] [Img – BCKESwXX-1611645795044] [img- BCKESwXX-1611645795044] Day 10 project summary (Spring Security Oauth2 JWT). The assets / 20210126123724219 PNG)]
The client Id and password match the client Id and password in the database oAUTH_client_DETAILS table.
Click Send: Token application is successful, as shown below:
The following data is returned:
parameter | Parameter is introduced |
---|---|
Access_token: | The access token, carries this token to access resources |
Token_type: | There are two types of MAC Token and Bearer Token, both of which have different verification algorithms. RFC 6750 recommends Oauth2 to be adopted |
Bearer Token: | (www.rfcreader.com/#rfc6750). |
Refresh_token: | The refresh tokenTo extend the expiration time of the access token |
Expires_in: | Scope, consistent with the defined client scope |
Scope: | Scope, consistent with the defined client scope |
Jit: | Unique identifier of the current token |
(3) Token check
Spring Security Oauth2 provides endpoints for validation tokens as follows:
# Post request:
http://localhost:9001/oauth/check_token?token= [access_token]
Copy the code
parameter | Parameter is introduced |
---|---|
access_token | The access token |
The postman test is as follows:
If the token verification fails, the following result occurs:
If the token expires, the result is as follows:
[Img-fjapgliu -1611645795047] [Img-fjapgliu -1611645795047] Day 10 project summary (Spring Security Oauth2 JWT). The assets / 20210126124633718 PNG)]
(4) Refresh the token
Refreshing a token is to generate a new token when the token is about to expire. It is different from token generation by authorization code and password authorization. Refreshing a token does not require authorization code, account number and password, but only a refreshing token, client ID and client password.
# Post request:
http://localhost:9001/oauth/token
Copy the code
parameter | Parameter is introduced |
---|---|
Grant_type: | Fixed for refresh_token |
Refresh_token: | Refresh token (note not access_token, refresh_token) |
3.2 Implementation of password Authorization Mode
(1) certification
The difference between the Resource Owner Password Credentials mode and the authorization code mode is that the authentication codes are no longer used to apply for tokens. Instead, the user name and Password are used to apply for tokens.
# Post request:
http://localhost:9001/oauth/token
Copy the code
Request parameters | Parameter is introduced |
---|---|
Grant_type: | Password Mode Authorization Enter the password |
Username: | account |
Password: | password |
And this link requires HTTP Basic authentication:
The test results are as follows:
(2) Verify the token
Spring Security Oauth2 provides endpoints for validation tokens as follows:
# Get request
http://localhost:9001/oauth/check_token?token=
Copy the code
Request parameters | Parameter is introduced |
---|---|
Token: | The token |
The postman test is as follows:
Return result:
{
"companyId": null."userpic": null."scope": [
"app"]."name": null."utype": null."active": true."id": null."exp": 1990221534."jti": "5b96666e-436b-4301-91b5-d89f9bbe6edb"."client_id": "changgou"."username": "szitheima"
}
Copy the code
- exp: Expiration time, type long, number of seconds since 1970 (
new Date().getTime()
Get the number of milliseconds from 1970.) - User_name: indicates the user name
- Client_id: indicates the client Id, configured in oauth_client_details
- Scope: Client scope, configured in the oauth_client_details table
- Jti: unique identifier corresponding to the token companyId, userpic, name, uType,
- Id: These fields are the user identity information extended by the authentication service based on Spring Security
(3) Refresh the token
Refreshing a token is to generate a new token when the token is about to expire. It is different from token generation by authorization code and password authorization. Refreshing a token does not require authorization code, account number and password, but only a refreshing token, client ID and client password.
# Post request
http://localhost:9001/oauth/token
Copy the code
Request parameters | Parameter is introduced |
---|---|
Grant_type: | Fixed for refresh_token |
Refresh_token: | Refresh token (note not access_token, refresh_token) |
A successful refresh of the token generates a new access token and a refresh token, which also has a longer validity period than the old token.
Refreshing a token is usually done when the token is about to expire.
3.3 summarize
Oauth2 has the following authorization modes:
- 1. Authorization Code[common]
- 2. Implicit Authorization patterns
- 3. Resource Owner Password Credentials[common]
- 4. Client Credentials
Entitlement code Indicates the authorization mode
1. To apply for authorization code Get: http://localhost:9001/oauth/authorize? client_id=changgou&response_type=code&scop=app&redirect_uri=http://localhost 2. On the login page, enter the client ID and key. 3. Click Authorize to obtain the authorization code 4. User authorization code for the token: Post: http://localhost:9001/oauth/tokenCopy the code
Password authorization Mode
The client ID and key must be transferred to the background. 2. The user account and password must be transferred to the backgroundCopy the code
4. Authorize resource services
4.1 Resource Service Authorization Process
(1) Traditional authorization process
Resource server authorization process as shown above, the client go to the license server application token, a token of your application, carrying the token access server resources, resource server access authorization service check the legitimacy of the token, authorized service returns the check result, if check successful will be returned to the user information to server resources, resource server if received by the calibration results, The resource is returned to the client.
The problem of traditional authorization methods is that each time a user requests resource services, the resource service needs to carry a token to access the authentication service to verify the validity of the token and obtain user information according to the token, which results in low performance.
(2) Public and private key authorization process
Pattern of traditional authorization of poor performance, every time need to request the authorization service check token validity, we can use the public key token is completed by the private key encryption, if its successful, says token, if the encrypted decryption failure, said the token is invalid, illegal, legally allowed to access the server resources, decryption failure, Access to resource server resources is not allowed.
The business process in the figure above is as follows:
- 1. The client requests the authentication service to apply for a token
- 2. Authentication service generates tokens The authentication service uses asymmetric encryption algorithms and private keys to generate tokens.
- 3. The client carries a token to access resource services. The client has added the following Authorization: Bearer tokens in the Http header.
- 4. The resource service requests the authentication service to verify the validity of the token. The resource service receives the token and verifies the validity of the token using the public key.
- 5. The token is valid and the resource service responds to the resource information to the client
4.2 Public and Private Keys
In the era of symmetric encryption, encryption and decryption used the same key, which was used for both encryption and decryption. There’s an obvious downside to this. If you transfer files between two people, both of them need to know the key. If it’s three, what about five? Asymmetric encryption results, with one key for encryption (public key) and another key for decryption (private key).
4.2.1 Principles of Public and Private Keys
Joe has two keys, one is public and the other is private.
Zhang SAN gives the public key to his friends —- Li Si, Wang Wu, zhao Liu —- each one.
Li Si is going to write a confidential letter to Zhang SAN. After ta is written, it can be encrypted with Zhang SAN’s public key to achieve the effect of confidentiality.
Zhang SAN receives the letter, decrypts it with a private key, and sees the contents of the letter. It is important to emphasize that as long as the private key is not compromised, the letter is secure and cannot be decrypted even if it falls into someone else’s hands.
Zhang SAN wrote back to Li Si and decided to use “digital signature”. After writing the letter, he uses a Hash function to generate a digest. Zhang SAN attached his signature to the letter and sent it to Li Si.
Li Si receives the letter, takes off the digital signature, decrypts it with Zhang SAN’s public key, and gets a summary of the letter. This proves that this letter is indeed sent by Zhang SAN. The Hash function is then used on the letter itself to compare the result with the summary obtained in the previous step. If they agree, it means the letter hasn’t been redacted.
4.2.2 Generating a Private Key Public Key
Spring Security provides support for JWT. In this section, we use JwtHelper provided by Spring Security to create JWT tokens, verify JWT tokens, and so on. Here we use asymmetric algorithm to encrypt JWT tokens, so we want to encrypt them into public and private keys.
(1) Generate a key certificate The following command generates a key certificate using the RSA algorithm. Each certificate contains a public key and a private key
Create a folder where you can run the following CMD command:
keytool -genkeypair -alias changgou -keyalg RSA -keypass changgou -keystore changgou.jks -storepass changgou
Copy the code
Keytool is a certificate management tool provided by Java. The parameters are as follows:
The command parameter | Parameter is introduced |
---|---|
– alias: | Alias for the key: Changgou |
– keyalg: | The hash algorithm used is RSA |
– keypass: | Access password for the key: Changgou |
– the keystore: | Keystore file name, xc.keystore holds the generated certificate: Changgo.jks |
– the storepass: | Key store access password: Changgou, |
This command is executed in the resources directory of the project:
(2) Query the certificate information
keytool -list -keystore changgou.jks
Copy the code
(3) Delete the alias.
keytool -delete -alias changgou -keystore changgou.jsk
Copy the code
4.2.3 Exporting a Public Key
Public keys everywhere require openSSL, an encryption and decryption toolkit that uses OpenSSL to export public key information.
Install Openssl: slproweb.com/products/Wi…
After downloading, execute the win64OpenSSL-1_1_0g. exe file to install!
After the installation is complete, configure the Path environment variable of OpenSSL, as shown below:
CMD go to the changgou. JKS file directory and run the following command:
keytool -list -rfc --keystore changgou.jks | openssl x509 -inform pem -pubkey
Copy the code
The execution result is shown as follows:
The following is a public key:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlv4lApm1ABDfWaXKuYh0
KcYCX4AFLbQMBKIbeFeNHWymqCnDdLsZkrZ2+oDKrx2tMQ0T4yDGoYHw+e2HgQqN
qZNHrHoxAC58wkTwzs4sEBt6Y7nYvO52c7Qdtpmx+nLIQOmUhq2QsDwVd1urw2LS
tOdwW1zwZh8EGbxMbmzrpKiGjKYgQFv9l+tFjDhTtEVgALPCDLG05qAo6TwuFDby
to0rICZ1VlHBf9V9gYE5dtVIyp+2e8MxRQgMbYVYJTr8/+h/nmELfwqLqVbMUqAK
YFbc0a8XOzYvEdS9bwAXG3aNv/5NbhAARWAhb1KS0uMckcXw7tB/8AchdVVocfkN
/wIDAQAB
-----END PUBLIC KEY-----
Copy the code
Copy the above public key into the text public. Key file and merge it into a single line for the project that needs to implement authorization.
4.2.4 JWT token
(1) Create token data
In changgou – user – create a test class com oauth2 engineering. Changgou. Token. CreateJwtTest, use it to create the token information, code is as follows:
/ * * *@Auther: csp1999
* @Date: 2021/01/25/7:30 *@Description: * /
public class CreateJwtTest {
/** * Create token test */
@Test
public void testCreateToken(a){
// Certificate file path
String key_location="changgou.jks";
// Keystore password
String key_password="changgou";
// The key password
String keypwd = "changgou";
// Alias of the key
String alias = "changgou";
// Access certificate path: Load the certificate
ClassPathResource resource = new ClassPathResource(key_location);
// Create a key factory: read the certificate data
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(resource,key_password.toCharArray());
// Read the key pair (public, private)
KeyPair keyPair = keyStoreKeyFactory.getKeyPair(alias,keypwd.toCharArray());
// Get private key -> RSA algorithm
RSAPrivateKey rsaPrivate = (RSAPrivateKey) keyPair.getPrivate();
/ / define content
Map<String, Object> tokenMap = new HashMap<>();
tokenMap.put("id"."1");
tokenMap.put("name"."itheima");
tokenMap.put("roles"."ROLE_VIP,ROLE_USER");
// Encrypt: generate a Jwt token
Jwt jwt = JwtHelper.encode(JSON.toJSONString(tokenMap), new RsaSigner(rsaPrivate));
// Retrieve the token
String encoded = jwt.getEncoded();
// Test the output
System.out.println(encoded);
/ / the result:
// eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlcyI6IlJPTEVfVklQLFJPTEVfVVNFUiIsIm5hbWUiOiJpdGhlaW1hIiwiaWQiOiIxIn0.IR9Qu9Z qYZ2gU2qgAziyT38UhEeL4Oi69ko-dzC_P9-Vjz40hwZDqxl8wZ-W2WAw1eWGIHV1EYDjg0-eilogJZ5UikyWw1bewXCpvlM-ZRtYQQqHFTlfDiVcFetyTay askwa-x_BVS4pTWAskiaIKbKR4KcME2E5o1rEek-3YPkqAiZ6WP1UOmpaCJDaaFSdninqG0gzSCuGvLuG40x0Ngpfk7mPOecsIi5cbJElpdYUsCr9oXc53RO yfvYpHjzV7c2D5eIZu3leUPXRvvVAPJFEcSBiisxUSEeiGpmuQhaFZd1g-yJ1WQrixFvehMeLX2XU6W1nlL5ARTpQf_Jjiw
}
/*** * Parse validation token tests */
@Test
public void testParseToken(a){
/ / token
String token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlcyI6IlJPTEVfVklQLFJPTEVfVVNFUiIsIm5hbWUiOiJpdGhlaW1hIiwiaWQiOiIxIn0.IR9Qu9 ZqYZ2gU2qgAziyT38UhEeL4Oi69ko-dzC_P9-Vjz40hwZDqxl8wZ-W2WAw1eWGIHV1EYDjg0-eilogJZ5UikyWw1bewXCpvlM-ZRtYQQqHFTlfDiVcFetyTa yaskwa-x_BVS4pTWAskiaIKbKR4KcME2E5o1rEek-3YPkqAiZ6WP1UOmpaCJDaaFSdninqG0gzSCuGvLuG40x0Ngpfk7mPOecsIi5cbJElpdYUsCr9oXc53R OyfvYpHjzV7c2D5eIZu3leUPXRvvVAPJFEcSBiisxUSEeiGpmuQhaFZd1g-yJ1WQrixFvehMeLX2XU6W1nlL5ARTpQf_Jjiw";
/ / the public key
String publickey = "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvFsEiaLvij9C1Mz+oyAmt47whAaRkRu/8kePM+X8760UGU0RMwGti6Z9y3LQ0RvK6I0b rXmbGB/RsN38PVnhcP8ZfxGUH26kX0RK+tlrxcrG+HkPYOH4XPAL8Q1lu1n9x3tLcIPxq8ZZtuIyKYEmoLKyMsvTviG5flTpDprT25unWgE4md1kthRWXOnf WHATVY7Y/r4obiOL1mS5bEa/iNKotQNnvIAKtjBM4RlIDWMa6dmz+lHtLtqDD2LF1qwoiSIHI75LQZ/CNYaHCfZSxtOydpNKq8eb1/PGiLNolD4La2zf0/1d lcr5mkesV570NxRmU1tFm8Zd3MZlZmyv9QIDAQAB-----END PUBLIC KEY-----";
/ / check Jwt
Jwt jwt = JwtHelper.decodeAndVerify(token, new RsaVerifier(publickey));
// Get the original Jwt content
String claims = jwt.getClaims();
System.out.println(claims);// {"roles":"ROLE_VIP,ROLE_USER","name":"itheima","id":"1"}
/ / JWT token
//String encoded = jwt.getEncoded();
//System.out.println(encoded);}}Copy the code
5. Set up an authentication server
5.1 Requirement Analysis
The flow chart of user login is as follows:
Execution process:
- 1. The user logs in and requests the authentication service
- 2. The authentication service passes the authentication, generates the JWT token, and writes the JWT token and related information into the cookie
- 3. The user accesses the resource page and takes the cookie to the gateway
- 4. The gateway obtains the token from the cookie. If the token exists, the gateway verifies the validity of the token
- 5. The user exits, requests the authentication service, and deletes the token in the cookie
5.2 Establish auTH certification microservice project
The authentication service provides the following functions:
1. Login interface
- The front-end post submits the account, password, etc., verifies the user identity, generates the token, and writes the token into the cookie.
2. Exit the interface
- Verify that the current user is legitimate and logged in. The token is removed from the cookie.
application.yml
server:
# Authenticate microservice port
port: 9001
# Spring related configuration
spring:
application:
# Micro service name
name: user-auth
# Redis configuration
redis:
# Redis database index (default 0)
database: 0
# Redis server address
host: 8.13166.136.
# Redis server connection port
port: 6379
# Redis server connection password (default null)
password: csp19990129
# Database configuration
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: JDBC: mysql: / / 127.0.0.1:3306 / changgou_oauth? useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=UTC
username: root
password: root
main:
allow-bean-definition-overriding: true
# Eureka configuration
eureka:
instance:
prefer-ip-address: true
client:
service-url:
defaultZone: http://127.0.0.1:7001/eureka
# auth related configuration
auth:
The expiration time of the token stored to Redis
ttl: 3600
# unique ID of client
clientId: changgou
# client key
clientSecret: changgou
# the cookie domain name
cookieDomain: localhost
cookieMaxAge: - 1
Configure the local certificate, key, and certificate password
encrypt:
key-store:
Certificate path (resources path)
location: classpath:/changgou.jks
# key
The public key (provided to each microservice) can be encrypted to verify the validity of the token --> the private key (provided to the authentication microservice) can be decrypted to generate the token --> the asymmetric encryption algorithm RSA
# We did MD5 encryption algorithm before, using digest encryption algorithm, irreversible!
# AES/DESC uses symmetric encryption, can encrypt and decrypt, encryption decrypt key is the same!
secret: changgou
# certificate alias
alias: changgou
# certificate password
password: changgou
Copy the code
Toolclass encapsulation
Create com. Changgou. Request. Util. AuthToken type, the user token data storage. The code is as follows:
AuthToken
/ * * *@Author: csp1999
* @Date: 2020/5/18 any valiant man *@Description: User token encapsulates utility class */
public class AuthToken implements Serializable {
/** * token information */
String accessToken;
/** * Refresh token(refresh_token) */
String refreshToken;
/** * JWT short token */
String jti;
public String getAccessToken(a) {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public String getRefreshToken(a) {
return refreshToken;
}
public void setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}
public String getJti(a) {
return jti;
}
public void setJti(String jti) {
this.jti = jti; }}Copy the code
Create com. Changgou. Request. Util. CookieUtil class, Cookie, operation code is as follows:
CookieUtil
/ * * *@Author: csp1999
* @Date: 2020/5/18 any valiant man *@Description: CookieUtil tool class */
public class CookieUtil {
/** * Set cookie **@param response
* @paramName Cookie name *@paramThe value the cookie value *@paramMaxAge Cookie life cycle is in seconds */
public static void addCookie(HttpServletResponse response, String domain, String path, String name,
String value, int maxAge, boolean httpOnly) {
Cookie cookie = new Cookie(name, value);
cookie.setDomain(domain);
cookie.setPath(path);
cookie.setMaxAge(maxAge);
cookie.setHttpOnly(httpOnly);
response.addCookie(cookie);
}
/** * Reads cookie ** based on cookie name@param request
* @return map<cookieName, cookieValue>
*/
public static Map<String, String> readCookie(HttpServletRequest request, String... cookieNames) {
Map<String, String> cookieMap = new HashMap<String, String>();
Cookie[] cookies = request.getCookies();
if(cookies ! =null) {
for (Cookie cookie : cookies) {
String cookieName = cookie.getName();
String cookieValue = cookie.getValue();
for (int i = 0; i < cookieNames.length; i++) {
if(cookieNames[i].equals(cookieName)) { cookieMap.put(cookieName, cookieValue); }}}}returncookieMap; }}Copy the code
Create com. Changgou. Request. Util. UserJwt class that encapsulates the User information in SpringSecurity and User’s basic information, the code is as follows:
UserJwt
/ * * *@Author: csp1999
* @Date: 2020/5/18 any valiant man *@Description: an object that encapsulates user information for storage in JWT */
public class UserJwt extends User {
/** * user ID */
private String id;
/** * User name */
private String name;
/** ** set company */
private String company;
/** * address */
private String address;
public UserJwt(String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
public String getId(a) {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName(a) {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCompany(a) {
return company;
}
public void setCompany(String company) {
this.company = company;
}
public String getAddress(a) {
return address;
}
public void setAddress(String address) {
this.address = address; }}Copy the code
Config package config class
AuthorizationServerConfig
@Configuration
@EnableAuthorizationServer
class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
/** * data source, used to fetch data from the database for authentication operations, tests can be fetched from memory */
@Autowired
private DataSource dataSource;
/** * JWT token converter */
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
/** * SpringSecurity user - defined authorization */
@Autowired
UserDetailsService userDetailsService;
/** * Authorization manager */
@Autowired
AuthenticationManager authenticationManager;
/** * token persistent storage interface */
@Autowired
TokenStore tokenStore;
@Autowired
private CustomUserAuthenticationConverter customUserAuthenticationConverter;
/** * Client information configuration **@param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("changgou") // Client ID
.secret("changgou") / / the secret key
.redirectUris("http://localhost") // Redirect the address
.accessTokenValiditySeconds(3600) // Access the token validity period
.refreshTokenValiditySeconds(3600) // Refresh the token validity period
.authorizedGrantTypes(
"authorization_code".// Generate tokens based on authorization codes
"client_credentials".// Client authentication
"refresh_token".// Refresh the token
"password") // Password authentication
.scopes("app"); // Client scope, name user-defined, mandatory
}
/** * Authorization server endpoint configuration **@param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.accessTokenConverter(jwtAccessTokenConverter)
.authenticationManager(authenticationManager) // Authentication manager
.tokenStore(tokenStore) // Token store
.userDetailsService(userDetailsService); // User information service
}
/** * Authorization server security configuration **@param oauthServer
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.allowFormAuthenticationForClients()
.passwordEncoder(new BCryptPasswordEncoder())
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
/** * Read the key configuration **@return* /
@Bean("keyProp")
public KeyProperties keyProperties(a) {
return new KeyProperties();
}
@Resource(name = "keyProp")
private KeyProperties keyProperties;
/** * Client configuration */
@Bean
public ClientDetailsService clientDetails(a) {
return new JdbcClientDetailsService(dataSource);
}
@Bean
@Autowired
public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
return new JwtTokenStore(jwtAccessTokenConverter);
}
/** * JWT token converter **@param customUserAuthenticationConverter
* @return* /
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(CustomUserAuthenticationConverter customUserAuthenticationConverter) {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
KeyPair keyPair = new KeyStoreKeyFactory(
keyProperties.getKeyStore().getLocation(), // Certificate path changgo.jks
keyProperties.getKeyStore().getSecret().toCharArray()) // Certificate key changgouapp
.getKeyPair(
keyProperties.getKeyStore().getAlias(), // Certificate alias changgou
keyProperties.getKeyStore().getPassword().toCharArray()); // Certificate password changgou
converter.setKeyPair(keyPair);
/ / configure custom CustomUserAuthenticationConverter
DefaultAccessTokenConverter accessTokenConverter = (DefaultAccessTokenConverter) converter.getAccessTokenConverter();
accessTokenConverter.setUserTokenConverter(customUserAuthenticationConverter);
returnconverter; }}Copy the code
CustomUserAuthenticationConverter
@Component
public class CustomUserAuthenticationConverter extends DefaultUserAuthenticationConverter {
@Autowired
UserDetailsService userDetailsService;
@Override
publicMap<String, ? > convertUserAuthentication(Authentication authentication) { LinkedHashMap response =new LinkedHashMap();
String name = authentication.getName();
response.put("username", name);
Object principal = authentication.getPrincipal();
UserJwt userJwt = null;
if (principal instanceof UserJwt) {
userJwt = (UserJwt) principal;
} else {
// Refresh_token by default does not call the userdetailService to get user information. Here we call it manually to get UserJwt
UserDetails userDetails = userDetailsService.loadUserByUsername(name);
userJwt = (UserJwt) userDetails;
}
response.put("name", userJwt.getName());
response.put("id", userJwt.getId());
response.put("company", userJwt.getCompany());
response.put("address", userJwt.getAddress());
if(authentication.getAuthorities() ! =null && !authentication.getAuthorities().isEmpty()) {
response.put("authorities", AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
}
returnresponse; }}Copy the code
UserDetailsServiceImpl
/** * Custom authentication */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
ClientDetailsService clientDetailsService;
/** * Custom authentication **@param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// Retrieve the identity. If the identity is empty, there is no authentication
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Client_id and client_secret are stored in httpBasic. Client_id and client_secret are authenticated
if (authentication == null) {
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(username);
if(clientDetails ! =null) {
/ / the secret key
String clientSecret = clientDetails.getClientSecret();
// Static mode
return new User(username, new BCryptPasswordEncoder().encode(clientSecret), AuthorityUtils.commaSeparatedStringToAuthorityList(""));
// Database lookup
//return new User(username,clientSecret, AuthorityUtils.commaSeparatedStringToAuthorityList(""));}}if (StringUtils.isEmpty(username)) {
return null;
}
// Query user information based on the user name
String pwd = new BCryptPasswordEncoder().encode("szitheima");
// Create the User object
String permissions = "goods_list,seckill_list";
UserJwt userDetails = new UserJwt(username, pwd, AuthorityUtils.commaSeparatedStringToAuthorityList(permissions));
//userDetails.setComy(songsi);
returnuserDetails; }}Copy the code
WebSecurityConfig
@Configuration
@EnableWebSecurity
@Order(-1)
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/** * ignores the security-blocked URL **@param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(
"/user/login"."/user/logout");
}
/** * Create an authorization management authentication object **@return
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean(a) throws Exception {
AuthenticationManager manager = super.authenticationManagerBean();
return manager;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
}
/** * Use BCryptPasswordEncoder to encode the password **@return* /
@Bean
public PasswordEncoder passwordEncoder(a) {
return new BCryptPasswordEncoder();
}
/ * * *@param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.httpBasic() // Enable Http basic authentication
.and()
.formLogin() // Enable form authentication
.and()
.authorizeRequests() // Restrict access based on Request
.anyRequest()
.authenticated(); // All other requests need to be validated}}Copy the code
The service layer
As shown in the figure above, we now implement an authentication process. The user enters the account password from the page to the Controller layer of authentication Service. The Controller layer invokes the Service layer, and the Service layer invokes the authentication address of OAuth2.0 for password authentication. It returns the token information to the Service layer, which sends the token information to the Controller layer, which stores the data in a Cookie and responds to the user.
Create com. Changgou. Request. Service. AuthService interface, and add the authorization method:
/ * * *@Author: csp1999
* @Date: 2020/7/7 shew forth *@Description: * /
public interface LoginService {
/** * Simulates the user's behavior by sending a request for a token return *@paramUsername username *@paramPassword Indicates the user password *@paramClientId clientId *@paramClientSecret Client key *@paramGrandType Authentication and authorization type: password Password authorization mode *@return* /
AuthToken login(String username, String password, String clientId, String clientSecret, String grandType);
}
Copy the code
Create com. Changgou. Request. Service. Impl. AuthServiceImpl implementation class, realize the access token data, authentication access token used here is that the password authorization model, USES the RestTemplate launched an authentication request, the request service code is as follows:
AuthService interface implementation class
/ * * *@Author: csp1999
* @Date: 2020/7/7 shew forth *@Description: * /
@Service
public class AuthServiceImpl implements AuthService {
@Autowired
private LoadBalancerClient loadBalancerClient;
@Autowired
private RestTemplate restTemplate;
/** * Authorization authentication method **@param username
* @param password
* @param clientId
* @param clientSecret
* @return* /
@Override
public AuthToken login(String username, String password, String clientId, String clientSecret) {
// Apply for a token
AuthToken authToken = applyToken(username, password, clientId, clientSecret);
if (authToken == null) {
throw new RuntimeException("Token application failed");
}
return authToken;
}
/** * Authentication method **@paramUsername: indicates the login name *@paramPassword: indicates the user password *@paramClientId: clientId * in the configuration file@paramClientSecret: the key * in the configuration file@return* /
private AuthToken applyToken(String username, String password, String clientId, String clientSecret) {
// Select the address of the authentication service
ServiceInstance serviceInstance = loadBalancerClient.choose("user-auth");
if (serviceInstance == null) {
throw new RuntimeException("No service found.");
}
// Get the url of the token
String path = serviceInstance.getUri().toString() + "/oauth/token";
/ / define the body
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
// Authorization mode
formData.add("grant_type"."password");
/ / account
formData.add("username", username);
/ / password
formData.add("password", password);
/ / define head
MultiValueMap<String, String> header = new LinkedMultiValueMap<>();
header.add("Authorization", httpbasic(clientId, clientSecret));
// Specify that the restTemplate does not throw an exception when it encounters a 400 or 401 response and returns a normal value
restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
@Override
public void handleError(ClientHttpResponse response) throws IOException {
If the value of the response is 400 or 401, do not throw an exception
if(response.getRawStatusCode() ! =400&& response.getRawStatusCode() ! =401) {
super.handleError(response); }}}); Map map =null;
try {
// HTTP requests the request token interface of Spring Security
ResponseEntity<Map> mapResponseEntity = restTemplate.exchange(path, HttpMethod.POST, new HttpEntity<MultiValueMap<String, String>>(formData, header), Map.class);
// Get the response data
map = mapResponseEntity.getBody();
} catch (RestClientException e) {
throw new RuntimeException(e);
}
if (map == null || map.get("access_token") = =null || map.get("refresh_token") = =null || map.get("jti") = =null) {
// JTI is the unique identifier of the JWT token as the user identity token
throw new RuntimeException("Failed to create token!");
}
// Encapsulate the response data into an AuthToken object
AuthToken authToken = new AuthToken();
// Access token (JWT)
String accessToken = (String) map.get("access_token");
// Refresh token (JWT)
String refreshToken = (String) map.get("refresh_token");
// jti, as the user's identity
String jwtToken = (String) map.get("jti");
authToken.setJti(jwtToken);
authToken.setAccessToken(accessToken);
authToken.setRefreshToken(refreshToken);
return authToken;
}
/** * Base64 encoding **@param clientId
* @param clientSecret
* @return* /
private String httpbasic(String clientId, String clientSecret) {
// Combine the client ID with the client password, and press "Client ID: Client password".
String string = clientId + ":" + clientSecret;
// Base64 encoding
byte[] encode = Base64Utils.encode(string.getBytes());
return "Basic " + newString(encode); }}Copy the code
LoginService interface implementation class
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
private RestTemplate restTemplate;
@Autowired
private LoadBalancerClient loadBalancerClient;
@Override
public AuthToken login(String username, String password, String clientId, String clientSecret, String grandType) {
// 1. Define the URL (the URL to apply for the token)
// Parameter: micro service name Spring.appplication Specifies the name
ServiceInstance choose = loadBalancerClient.choose("user-auth");
String url =choose.getUri().toString()+"/oauth/token";
// 2. Define header information (including client ID and client secr)
MultiValueMap<String,String> headers = new LinkedMultiValueMap<>();
headers.add("Authorization"."Basic "+Base64.getEncoder().encodeToString(new String(clientId+":"+clientSecret).getBytes()));
// 3. Define the name and password of the user with authorization mode in the request body
MultiValueMap<String,String> formData = new LinkedMultiValueMap<>();
formData.add("grant_type",grandType);
formData.add("username",username);
formData.add("password",password);
// 4. The simulated browser sends a POST request to the authentication server carrying the header and request body
/** * Parameter 1 specifies the URL of the request to be sent * Parameter 2 specifies the method of the request to be sent PSOT * parameter 3 specifies the request entity (containing header and request body data) */
HttpEntity<MultiValueMap> requestentity = new HttpEntity<MultiValueMap>(formData,headers);
ResponseEntity<Map> responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestentity, Map.class);
// 5. The response is received (i.e., the token information)
Map body = responseEntity.getBody();
// Encapsulate once.
AuthToken authToken = new AuthToken();
// Access token (JWT)
String accessToken = (String) body.get("access_token");
// Refresh token (JWT)
String refreshToken = (String) body.get("refresh_token");
// jti, as the user's identity
String jwtToken= (String) body.get("jti");
authToken.setJti(jwtToken);
authToken.setAccessToken(accessToken);
authToken.setRefreshToken(refreshToken);
/ / 6. Return
return authToken;
}
public static void main(String[] args) {
byte[] decode = Base64.getDecoder().decode(new String("Y2hhbmdnb3UxOmNoYW5nZ291Mg==").getBytes());
System.out.println(newString(decode)); }}Copy the code
The controller layer
Create control layer com. Changgou. Request. Controller. AuthController, writing user login authorization method, the code is as follows:
AuthController
/ * * *@Author: csp1999
* @Date: 2020/7/7 shew forth *@Description: * /
@RestController
@RequestMapping(value = "/userx")
public class AuthController {
// Client ID
@Value("${auth.clientId}")
private String clientId;
/ / the secret key
@Value("${auth.clientSecret}")
private String clientSecret;
// The domain name of the Cookie
@Value("${auth.cookieDomain}")
private String cookieDomain;
// Cookie life cycle
@Value("${auth.cookieMaxAge}")
private int cookieMaxAge;
@Autowired
AuthService authService;
@PostMapping("/login")
public Result login(String username, String password) {
if (StringUtils.isEmpty(username)) {
throw new RuntimeException("User name cannot be empty");
}
if (StringUtils.isEmpty(password)) {
throw new RuntimeException("Passwords cannot be empty.");
}
// Apply for a token
AuthToken authToken = authService.login(username, password, clientId, clientSecret);
// User identity token
String access_token = authToken.getAccessToken();
// Store tokens to cookies
saveCookie(access_token);
return new Result(true, StatusCode.OK, "Login successful!");
}
/** * store token to cookie **@param token
*/
private void saveCookie(String token) { HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse(); CookieUtil.addCookie(response, cookieDomain,"/"."Authorization", token, cookieMaxAge, false); }}Copy the code
UserLoginController
/ * * *@Author: csp1999
* @Date: 2020/7/7 shew forth *@Description: * /
@RestController
@RequestMapping("/user")
public class UserLoginController {
@Autowired
private LoginService loginService;
/** * Client id */
@Value("${auth.clientId}")
private String clientId;
/** * Client key */
@Value("${auth.clientSecret}")
private String clientSecret;
/** * Authorization mode Password mode */
private static final String GRAND_TYPE = "password";
/**
* cookie域名
*/
@Value("${auth.cookieDomain}")
private String cookieDomain;
/** * Cookie life cycle */
@Value("${auth.cookieMaxAge}")
private int cookieMaxAge;
/** * Login method: * 1. Password mode Authentication and authorization mode: grant_type=password **@paramUsername 2. Szitheima *@paramPassword 3. Szitheima *@return* /
@RequestMapping("/login")
public Result<Map> login(String username, String password) {
// call the login method of loginService to login and return the generated token data
AuthToken authToken = loginService.login(username, password, clientId, clientSecret, GRAND_TYPE);
// Set to cookie
saveCookie(authToken.getAccessToken());
return new Result<>(true, StatusCode.OK, "Token generation successful", authToken);
}
// Save the token to the cookie
private void saveCookie(String token) { HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse(); CookieUtil.addCookie(response, cookieDomain,"/"."Authorization", token, cookieMaxAge, false); }}Copy the code
5.3 test
Visit: http://localhost:9001/user/login? username=szitheima&password=szitheima
The results are as follows:
Token generation successful! And you’re done!