1. Demand Analysis
2. msm module (send verification code)
1. Preparations
(1) Register Yunlian account and use verification code service
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:
- All requests go through the service gateway, which exposes services to the outside world and provides unified user authentication at the gateway.
- Since the gateway is going to authenticate users and the gateway knows which URLs to authenticate, we have to make rules for URLs
- 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; } }