06-Mobile Logon&token Generation&Cloud Message Authentication&User Authentication and Gateway Integration (Gateway does unified privilege authentication)

1. Demand Analysis

2. msm module (send verification code)

1. Preparations

(1) Register Yunlian account and use verification code service

Portal

Since Yunlianyun can't account for itself, but gives us a free SMS quota of 8 yuan, we can use it for free. Thanks to Yunlianyun, it is much better than the next cloud.

Follow my steps after registering ( Official JAVASDK Reference Steps)


Introducing a maven dependency on capacitive clouds in the role of modules

<dependencies>
        <!--Yunlian Cloud SMS Service-->
        <dependency>
            <groupId>com.cloopen</groupId>
            <artifactId>java-sms-sdk</artifactId>
            <version>1.0.4</version>
        </dependency>
    </dependencies>

Configure the Containment Cloud Sending Information service

    //Production environment request address: app.cloopen.com
    String serverIp = "app.cloopen.com";
    //Request Port
    String serverPort = "8883";
    //Main account, you can see the developer's main account ACCOUNT SID and main account token AUTH TOKEN on the first page of the console when you log on to the Cloud Communications website
    String accountSId = "accountSId";
    String accountToken = "accountToken";
    //Use the APPID of the created application in the Administration Console
    String appId = "appId";
    CCPRestSmsSDK sdk = new CCPRestSmsSDK();
    sdk.init(serverIp, serverPort);
    sdk.setAccount(accountSId, accountToken);
    sdk.setAppId(appId);
    sdk.setBodyType(BodyType.Type_JSON);
    String to = "1352*******";
    String templateId= "templateId";
    String[] datas = {"Variable 1","Variable 2","Variable 3"};
    String subAppend="1234";  //Optional 	 Extension code, four digits 0~9999
    String reqId="fadfafas";  //Optional third party custom message id, maximum support 32 digits in English, no duplication allowed for the same natural day under the same account
    //HashMap<String, Object> result = sdk.sendTemplateSMS(to,templateId,datas);
    HashMap<String, Object> result = sdk.sendTemplateSMS(to,templateId,datas,subAppend,reqId);
    if("000000".equals(result.get("statusCode"))){
        //Normally return output data package information (map)
    	HashMap<String,Object> data = (HashMap<String, Object>) result.get("data");
    	Set<String> keySet = data.keySet();
    	for(String key:keySet){
    		Object object = data.get(key);
    		System.out.println(key +" = "+object);
    	}
    }else{
    	//Exception returns output error code and error information
    	System.out.println("Error Code=" + result.get("statusCode") +" error message= "+result.get("statusMsg"));
    }

(2) Modify the configuration file

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

#Returns json's global time format
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8

spring.redis.host=192.168.44.165
spring.redis.port=6379
spring.redis.database= 0
spring.redis.timeout=1800000
spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-wait=-1
#Maximum blocking wait time (negative indicates no limit)
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0

# nacos service address
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848

# We can also write our data as a constant when we configure it with SMS dev. We are now reading the constant value in the configuration file using @value(${key}) externally.
app.cloopen.accountSId=d0
app.cloopen.accountToken=d224
app.cloopen.appId=800d7
app.cloopen.restDevUrl=https://app.cloopen.com

(3) Modify the startup class

Since we do not have a database in this module, we need to exclude automatic configuration of the database

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//Cancel data source auto-configuration
@EnableDiscoveryClient
public class ServiceMsmApplication {
   public static void main(String[] args) {
      SpringApplication.run(ServiceMsmApplication.class, args);
   }
}

(3) Configure our gateway

#Set routing id
spring.cloud.gateway.routes[3].id=service-msm
#Setting uri for routing
spring.cloud.gateway.routes[3].uri=lb://service-msm
#Set route assertion, proxy servicerId is/auth/path of auth-service
spring.cloud.gateway.routes[3].predicates= Path=/*/msm/**

2. Develop Authentication Code Sending Interface

(1) Create constant classes for reading configuration files

// Read the constant values defined by the configuration file, implement an interface, override the method, and implement springboot to read the load data as soon as it starts our constant
// value ${key} is injected into the property
//  Equivalent to defining a constant and calling it out
@Component
public class ConstantPropertiesUtils implements InitializingBean {

    @Value("${app.cloopen.accountSId}")
    private String accountSId;

    @Value("${app.cloopen.accountToken}")
    private String accountToken;

    @Value("${app.cloopen.appId}")
    private String appId;

    @Value("${app.cloopen.restDevUrl}")
    private String restDevUrl;

    /**
     * Define three constant properties to expose calls
     */
    public static String ACCOUNTS_ID;
    public static String ACCOUNT_TOKEN;
    public static String APP_ID;
    public static String REST_DEV_URL;

    /**
     * Load Execution Method
     * Assigning values to three variables
     *
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        ACCOUNTS_ID = accountSId;
        ACCOUNT_TOKEN = accountToken;
        APP_ID = appId;
        REST_DEV_URL = restDevUrl;
    }
}

(2) Encapsulation Verification Code Generation Class

Is the use of the Random class, splicing strings, returning results

public class RandomUtil {

    private static final Random random = new Random();

    private static final DecimalFormat fourdf = new DecimalFormat("0000");

    private static final DecimalFormat sixdf = new DecimalFormat("000000");

    public static String getFourBitRandom() {
        return fourdf.format(random.nextInt(10000));
    }

    public static String getSixBitRandom() {
        return sixdf.format(random.nextInt(1000000));
    }

    /**
     * Given an array, extract n data
     * @param list
     * @param n
     * @return
     */
    public static ArrayList getRandom(List list, int n) {

        Random random = new Random();

        HashMap<Object, Object> hashMap = new HashMap<Object, Object>();

// Generate random numbers and save them in HashMap
        for (int i = 0; i < list.size(); i++) {

            int number = random.nextInt(100) + 1;

            hashMap.put(number, i);
        }

// Importing Arrays from HashMap
        Object[] robjs = hashMap.values().toArray();

        ArrayList r = new ArrayList();

// Traverse the array and print the data
        for (int i = 0; i < n; i++) {
            r.add(list.get((int) robjs[i]));
            System.out.print(list.get((int) robjs[i]) + "\t");
        }
        System.out.print("\n");
        return r;
    }
}

(3) Controller implementation

@ApiOperation(value = "Send Authentication Code")
    @GetMapping("/send/{phone}")
    public Result sendCode(@PathVariable("phone") String phone) {
        // Check if redis has a verification code for this phone number
        String code = redisTemplate.opsForValue().get(phone);
        // The mobile number has sent a verification code and has not expired yet
        if (!StringUtils.isEmpty(code)) {
            return Result.ok();
        }
        // Send operation without sending Authentication Code
        // Get Authentication Code
        code = RandomUtil.getSixBitRandom();
        // Get the results sent
        boolean result = msmService.send(phone, code);

        if (result) {
            // Generate Authentication Code into redis, set valid time
            // Send successfully saved to redis, set save time to 2 minutes
            redisTemplate.opsForValue().set(phone, code, 2, TimeUnit.MINUTES);
            return Result.ok();
        } else {
            // fail in send
            return Result.fail().message("Sending SMS failed");
        }
    }

(4) service implementation

@Override
    public boolean send(String phone, String code) {
        if (StringUtils.isEmpty(phone) || StringUtils.isEmpty(code)) {
            return false;
        }
        // Integrate Junction Cloud to Send SMS
        // TODO Send SMS
        //Production environment request address: app.cloopen.com
        String serverIp = "app.cloopen.com";
        //Request Port
        String serverPort = "8883";
        //Main account, you can see the developer's main account ACCOUNT SID and main account token AUTH TOKEN on the first page of the console when you log on to the Cloud Communications website
        String accountSId = ConstantPropertiesUtils.ACCOUNTS_ID;
        String accountToken = ConstantPropertiesUtils.ACCOUNT_TOKEN;
        //Use the APPID of the created application in the Administration Console
        String appId = ConstantPropertiesUtils.APP_ID;
        CCPRestSmsSDK sdk = new CCPRestSmsSDK();
        sdk.init(serverIp, serverPort);
        sdk.setAccount(accountSId, accountToken);
        sdk.setAppId(appId);
        sdk.setBodyType(BodyType.Type_JSON);
        String to = phone;
        String templateId = "1";
        String[] datas = {code, "2"};
        HashMap<String, Object> result = sdk.sendTemplateSMS(to, templateId, datas);
        if ("000000".equals(result.get("statusCode"))) {
            //Normally return output data package information (map)
            HashMap<String, Object> data = (HashMap<String, Object>) result.get("data");
            Set<String> keySet = data.keySet();
            for (String key : keySet) {
                Object object = data.get(key);
                System.out.println(key + " = " + object);
            }
            return true;
        } else {
            //Exception returns output error code and error information
            System.out.println("Error Code=" + result.get("statusCode") + " error message= " + result.get("statusMsg"));
            // TODO Send SMS End
            return false;
        }
    }

3. User module (handling login requests)

1. Complete Token Generation with JWT

(1) Introduction to JWT

The JWT tool JWT (Json Web Token) is an open JSON-based standard implemented to deliver declarations between network applications.
JWT's declarations are typically used to transfer authenticated user identity information between identity providers and service providers in order to obtain resources from the resource server. For example, on user logins

The most important function of JWT is to prevent forgery of token information.

The principle of JWT, a JWT consists of three parts: the public part, the private part, and the signature part. Finally, the JWT is obtained by base64 encoding of these three combinations.

1. Public Section
This is mainly related to the configuration parameters of the JWT, such as signature encryption algorithm, format type, expiration time, and so on.
Key=ATGUIGU
2. Private Section
User-defined content, according to the actual needs of the real information to be encapsulated.
userInfo{User's Id, User's nickname nickName}
3. Signature Part
SaltiP: Ip address of the current server! {ip of proxy server configured in linux}
Primary user encrypts {salt value} when generating strings for JWT
Final composition key+salt+userInfo_token!
Base64 encoding is not encryption, it just turns plain text information into invisible strings. But in fact, you can decode the base64 encoding into plain text with only a few tools, so don't put private information in the JWT.

(2) Module integration JWT

Introducing JWT dependencies

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

(3) Write JWTHelper class to implement token generation and decryption

package com.atdk.yygh.common.helper;

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

import java.util.Date;


public class JwtHelper {
    /**
     * token Expiration Time
     */
    private static long tokenExpiration = 24 * 60 * 60 * 1000;
    /**
     * Signature Key
     */
    private static String tokenSignKey = "123456";

    /**
     * Generate token
     * If you modify it, you only need to modify it as needed. setSubject("YYGH-USER")
     * .claim("userId", userId)  // Set header information, you can set multiple
     * Other places can remain the same
     *
     * @param userId
     * @param userName
     * @return
     */
    public static String createToken(Long userId, String userName) {
        String token = Jwts.builder()
                .setSubject("YYGH-USER")
                .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration)) // Timing from current time
                .claim("userId", userId)  // Set header information, you can set multiple
                .claim("userName", userName)
                .signWith(SignatureAlgorithm.HS512, tokenSignKey)  // Set hash
                .compressWith(CompressionCodecs.GZIP)
                .compact();
        return token;
    }

    /**
     * More token to get user Id
     *
     * @param token
     * @return
     */
    public static Long getUserId(String token) {
        if (StringUtils.isEmpty(token)) return null;
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
        Claims claims = claimsJws.getBody();
        Integer userId = (Integer) claims.get("userId");
        return userId.longValue();
    }

    /**
     * More token to get user name
     *
     * @param token
     * @return
     */
    public static String getUserName(String token) {
        if (StringUtils.isEmpty(token)) return "";
        Jws<Claims> claimsJws
                = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
        Claims claims = claimsJws.getBody();
        return (String) claims.get("userName");
    }

    public static void main(String[] args) {
        String token = JwtHelper.createToken(1L, "55");
        System.out.println(token);
        System.out.println(JwtHelper.getUserId(token));
        System.out.println(JwtHelper.getUserName(token));
    }
}


2. Controller interface

We encapsulate the returned information in a map collection to make it easier for our front end to get the data

 @ApiOperation("User logon, mobile phone number verification number")
    @PostMapping("/login")
    public Result login(@RequestBody LoginVo loginVo) {
        Map<String, Object> map = userInfoService.login(loginVo);
        return Result.ok(map);
    }

3,service

 @Override
    public Map<String, Object> login(LoginVo loginVo) {
        // Determine if the mobile phone number is empty
        String phone = loginVo.getPhone();
        String code = loginVo.getCode();
        // Check parameters
        if (StringUtils.isEmpty(phone) || StringUtils.isEmpty(code)) {
            throw new YyghException(ResultCodeEnum.PARAM_ERROR);
        }

        // TODO check if the verification code is correct
        String mobleCode = redisTemplate.opsForValue().get(phone);
        if (!code.equals(mobleCode)) {
            throw new YyghException(ResultCodeEnum.CODE_ERROR);
        }

        // Verify the existence of mobile number
        QueryWrapper<UserInfo> wrapper = new QueryWrapper();
        wrapper.eq("phone", phone);
        UserInfo userInfo = baseMapper.selectOne(wrapper);
        // Add if it doesn't exist
        if (userInfo == null) {
            userInfo = new UserInfo();
            userInfo.setName("");
            userInfo.setPhone(phone);
            userInfo.setStatus(1);
            this.save(userInfo);
        }
        // Check if disabled
        if (userInfo.getStatus() == 0) {
            throw new YyghException(ResultCodeEnum.LOGIN_DISABLED_ERROR);
        }
        // TODO Record Logon Record

        // Return user-related information
        HashMap<String, Object> result = new HashMap<>();
        String name = userInfo.getName();
        // name set nickname to empty
        if (StringUtils.isEmpty(name)) {
            name = userInfo.getNickName();
        }
        // nickname set mobile number for empty
        if (StringUtils.isEmpty(name)) {
            name = userInfo.getPhone();
        }
        // TODO token generation calls tool class to complete token generation
        String token = JwtHelper.createToken(userInfo.getId(), name);
        result.put("name", name);
        result.put("token", token);
        return result;
    }

4. Integration of User Authentication and Gateway

1. Ideas

Ideas:

  1. All requests go through the service gateway, which exposes services to the outside world and provides unified user authentication at the gateway.
  2. Since the gateway is going to authenticate users and the gateway knows which URLs to authenticate, we have to make rules for URLs
  3. For asynchronous requests from Api interfaces, we take url rule matching, such as: /api//auth/, where user authentication is required for everything that meets this rule
    4. Personal feeling is the interceptor in spring, intercepts requests, validates requests, requests certification pass, goes to the next filter, fails certification, ends the request

2. Add a filter to the service gateway to filter requests and authenticate requests

Create classes, implement interfaces, override methods

public class AuthGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    ServerHttpRequest request = exchange.getRequest();
    String path = request.getURI().getPath();
    System.out.println("==="+path);

    //Internal service interface, no external access allowed
    if(antPathMatcher.match("/**/inner/**", path)) {
        ServerHttpResponse response = exchange.getResponse();
        return out(response, ResultCodeEnum.PERMISSION);
    }

    Long userId = this.getUserId(request);
    //api Interface, asynchronous request, validation user must log in
    if(antPathMatcher.match("/api/**/auth/**", path)) {
        if(StringUtils.isEmpty(userId)) {
            ServerHttpResponse response = exchange.getResponse();
            return out(response, ResultCodeEnum.LOGIN_AUTH);
        }
    }
    return chain.filter(exchange);
}

}

3. Determine user login status in service gateway

How to obtain user information in the gateway: 1, we uniformly obtain from the header header information how to judge the legitimacy of user information:
When we log in, we return to the user token. After we get the token in the service gateway, I go to redis to see how the user id exists, token is legal, otherwise it is illegal

/**
 * Get the current logged-in user id
 * @param request
 * @return
 */
private Long getUserId(ServerHttpRequest request) {
    String token = "";
    List<String> tokenList = request.getHeaders().get("token");
    if(null  != tokenList) {
        token = tokenList.get(0);
    }
    if(!StringUtils.isEmpty(token)) {
        return JwtHelper.getUserId(token);
    }
    return null;
}

Complete code

@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        System.out.println("===" + path);

        //Internal service interface, no external access allowed
        if (antPathMatcher.match("/**/inner/**", path)) {
            ServerHttpResponse response = exchange.getResponse();
            return out(response, ResultCodeEnum.PERMISSION);
        }

        Long userId = this.getUserId(request);
        //api Interface, asynchronous request, validation user must log in
        if (antPathMatcher.match("/api/**/auth/**", path)) {
            if (StringUtils.isEmpty(userId)) {
                ServerHttpResponse response = exchange.getResponse();
                return out(response, ResultCodeEnum.LOGIN_AUTH);
            }
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }

    /**
     * api Interface Authentication Failed Return Data
     *
     * @param response
     * @return
     */
    private Mono<Void> out(ServerHttpResponse response, ResultCodeEnum resultCodeEnum) {
        Result result = Result.build(null, resultCodeEnum);
        byte[] bits = JSONObject.toJSONString(result).getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        //Specify the encoding, otherwise the Chinese text will be scrambled in the browser
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }

    /**
     * Get the current logged-in user id
     * Get data token in request header
     *
     * @param request
     * @return
     */
    private Long getUserId(ServerHttpRequest request) {
        String token = "";
        List<String> tokenList = request.getHeaders().get("token");
        if (null != tokenList) {
            token = tokenList.get(0);
        }
        if (!StringUtils.isEmpty(token)) {
            return JwtHelper.getUserId(token);
        }
        return null;
    }
}


Keywords: Java

Added by itandme on Wed, 22 Dec 2021 09:24:09 +0200