目前大多項目是先後端分離。在後臺接口服務開發過程當中,每每咱們須要先搭建一個基礎服務,好比登陸註冊功能、自動對全部的接口進行token的安全校驗等,這樣能夠防範安全問題的出現。而且這樣後續的同事能夠只關注業務代碼的開發,不須要關心基礎架構服務的實現。javascript
此次我準備搭建一個簡單的後臺服務,用的是spring boot + mysql + jjwt + mybatis
。css
首先咱們使用IDEA自帶的初始化項目功能,建立一個Spring boot項目,如圖:html
或者在線生成,點擊進入java
pom.xmlmysql
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE</version> </parent> <groupId>com.zz</groupId> <artifactId>rest-api</artifactId> <version>0.0.1-SNAPSHOT</version> <name>rest-api</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.jayway.jsonpath</groupId> <artifactId>json-path</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> <version>2.1.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-rest</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.8.5</version> <scope>compile</scope> </dependency> <!-- Use MySQL Connector-J --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- image to base64 --> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency> <!-- jjwt支持 --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.4.0</version> </dependency> <!--commons-codec --> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> </dependency> <dependency> <groupId>com.github.terran4j</groupId> <artifactId>terran4j-commons-api2doc</artifactId> <version>1.0.2</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>com.itextpdf.tool</groupId> <artifactId>xmlworker</artifactId> <version>5.5.10</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>3.15</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-scratchpad</artifactId> <version>3.15</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> 複製代碼
maven更改成國內阿里雲鏡像,這樣比較快git
settings.xml:github
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd"> <mirrors> <mirror> <id>alimaven</id> <name>aliyun maven</name> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> <mirrorOf>central</mirrorOf> </mirror> </mirrors> </settings> 複製代碼
Mybatis 推薦插件以下:web
application.yml算法
# mysql spring: jpa: show-sql: true hibernate: ddl-auto: update servlet: multipart: max-file-size: 10MB max-request-size: 10MB profiles: active: dev # 靜態資源配置 mvc: static-path-pattern: /** resources: static-locations: file:/Users/wz/projects/blog/uploadFile/,classpath:/static/,classpath:/resources/,classpath:/file/,classpath:/templates/ mybatis-plus: mapper-locations: classpath:/mapper/*.xml type-aliases-package: com.zz.entity #自定義 my: tokenURL: "55555" authURL: "88888" 複製代碼
application-dev.ymlspring
# mysql spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: root password: ****** server: port: 8080 複製代碼
大概目錄結構以下
搭建細節再也不贅述;
com.zz.config.MyConfiguration
package com.zz.config; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; @Configuration @ConfigurationProperties(prefix = "my") public class MyConfiguration { private String tokenURL; private String authURL; public String getAuthURL() { return this.authURL; } public void setAuthURL(String authURL) { this.authURL = authURL; } public String getTokenURL() { return this.tokenURL; } public void setTokenURL(String tokenURL) { this.tokenURL = tokenURL; } } 複製代碼
com.zz.config.MyConfiguration
package com.zz.config; import com.zz.common.interceptor.AuthenticationInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { // 攔截全部請求,經過判斷是否有 @passToken 註解 決定是否須要跳過登陸 registry.addInterceptor(authenticationInterceptor()) .addPathPatterns("/**"); } @Bean public AuthenticationInterceptor authenticationInterceptor() { return new AuthenticationInterceptor(); } } 複製代碼
com.zz.model.Response
package com.zz.model; public class Response { private int code; private String msg; private Object data; public Object getData() { return data; } public void setData(Object data) { this.data = data; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } } 複製代碼
com.zz.utils.HttpUtils
獲取Request、 Response、session
package com.zz.utils; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; /** * 獲取 Request 和 Response */ public class HttpUtils { // 獲取 request public static HttpServletRequest getRequest() { ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes(); if (requestAttributes == null) return null; return requestAttributes.getRequest(); } // 獲取 response public static HttpServletResponse getResponse() { ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes(); if (requestAttributes == null) return null; return requestAttributes.getResponse(); } // 獲取 session public static HttpSession getSession(){ HttpServletRequest request = getRequest(); if(request == null) return null; return request.getSession(); } } 複製代碼
com.zz.utils.JWTUtils
JWT 生成token, 驗證token
package com.zz.utils; import com.zz.entity.User; import io.jsonwebtoken.*; import org.apache.commons.codec.binary.Base64; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.UUID; public class JWTUtils { // 生成簽名的時候使用的祕鑰secret private static final String SECRETKEY = "KJHUhjjJYgYUllVbXhKDHXhkSyHjlNiVkYzWTBac1Yxkjhuad"; // expirationDate 生成jwt的有效期,單位秒 private static long expirationDate = 2 * 60 * 60; /** * 由字符串生成加密key * * @return SecretKey */ private static SecretKey generalKey(String stringKey) { byte[] encodedKey = Base64.decodeBase64(stringKey); return new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES"); } /** * 建立 jwt * * @param user 登陸成功後的用戶信息 * @return jwt token */ public static String createToken(User user) { // 指定簽名的時候使用的簽名算法,也就是header那部分,jwt已經將這部份內容封裝好了 SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; // 生成JWT的時間 long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); // 建立payload的私有聲明(根據特定的業務須要添加,若是要拿這個作驗證,通常是須要和jwt的接收方提早溝通好驗證方式的) Map<String, Object> claims = new HashMap<>(); claims.put("userId", user.getUserId()); claims.put("userName", user.getUserName()); claims.put("password", user.getPassword()); // 生成簽名的時候使用的祕鑰secret,這個方法本地封裝了的,通常能夠從本地配置文件中讀取,切記這個祕鑰不能外露哦。它就是你服務端的私鑰,在任何場景都不該該流露出去。一旦客戶端得知這個secret, 那就意味着客戶端是能夠自我簽發jwt了 SecretKey key = generalKey(SECRETKEY + user.getPassword()); // 生成簽發人 // json形式字符串或字符串,增長用戶非敏感信息存儲,如用戶id或用戶帳號,與token解析後進行對比,防止亂用 HashMap<String, Object> storeInfo = new HashMap<String, Object>(); storeInfo.put("userId", user.getUserId()); storeInfo.put("userName", user.getUserName()); String subject = storeInfo.toString(); // 下面就是在爲payload添加各類標準聲明和私有聲明瞭 // 這裏其實就是new一個JwtBuilder,設置jwt的body JwtBuilder builder = Jwts.builder() // 若是有私有聲明,必定要先設置這個本身建立的私有的聲明,這個是給builder的claim賦值,一旦寫在標準的聲明賦值以後,就是覆蓋了那些標準的聲明的 .setClaims(claims) // 惟一隨機UUID // 設置JWT ID:是JWT的惟一標識,根據業務須要,這個能夠設置爲一個不重複的值,主要用來做爲一次性token,從而回避重放攻擊 .setId(UUID.randomUUID().toString()) // jwt的簽發時間 .setIssuedAt(now) // 表明這個JWT的主體,即它的全部人,這個是一個json格式的字符串,能夠存放什麼userid,roldid之類的,做爲何用戶的惟一標誌 .setSubject(subject) // 設置簽名使用的簽名算法和簽名使用的祕鑰 .signWith(signatureAlgorithm, key); if (expirationDate >= 0) { long expMillis = nowMillis + expirationDate * 1000; Date exp = new Date(expMillis); builder.setExpiration(exp); } return builder.compact(); } /** * 解密token,獲取聲明的實體 * * @param token 加密後的token * @return claims */ public static Claims parseToken(String token, User user) { // 簽名祕鑰,和生成的簽名的祕鑰要保持如出一轍 SecretKey key = generalKey(SECRETKEY + user.getPassword()); // 獲取私有聲明 Claims claims = Jwts.parser() // 設置簽名的祕鑰 .setSigningKey(key) // 設置須要解析的token .parseClaimsJws(token).getBody(); return claims; } /** * 校驗token * * @param token 加密後的token * @param user 用戶信息 * @return true|false */ public static Boolean verify(String token, User user) { // 獲取私有聲明的實體 Claims claims = parseToken(token, user); return claims.get("password").equals(user.getPassword()); } } 複製代碼
全部的服務查詢都採用統一的各自的實體類
好比:
com.zz.query.UserQuery
用戶查詢實體
package com.zz.query; public class UserQuery { private String userName; private String password; private long userId; private boolean showPassword; public boolean isShowPassword() { return showPassword; } public void setShowPassword(boolean showPassword) { this.showPassword = showPassword; } public long getUserId() { return userId; } public void setUserId(long userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } 複製代碼
全部的服務查詢返回都採用統一的各自的實體類
好比:
com.zz.entity.User
用戶數據返回實體
package com.zz.entity; public class User { private long userId; private String userName; private String token; private String password; public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getToken() { return token; } public void setToken(String token) { this.token = token; } public long getUserId() { return userId; } public void setUserId(long userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } } 複製代碼
咱們這採起的是三層架構:controller —> service —> mapper;
若是咱們要寫一個User類接口,先聲明一個UserController路由控制層,而後這個裏調用UserService實現類方法,而後再調用mapper持久層去CRUD(mysql增查刪改)。
基礎搭建先暫停,開始實質業務的推演;
mysql的鏈接就很少說啦;
讓咱們開始實現之旅吧;
com.zz.newController.UserController
用戶註冊
package com.zz.newController; import com.auth0.jwt.JWT; import com.auth0.jwt.interfaces.DecodedJWT; import com.zz.common.annotation.PassToken; import com.zz.common.base.BaseApplicationController; import com.zz.entity.User; import com.zz.model.Response; import com.zz.query.UserQuery; import com.zz.service.UserService; import com.zz.utils.JWTUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.Map; /** * 登陸 * author: wz */ @RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; /* * @param userName * @param password * @return response */ @PostMapping("/add") @PassToken public Response addUser(@RequestParam String userName, @RequestParam String password, Response response) { UserQuery query = new UserQuery(); User userData = null; query.setUserName(userName); query.setPassword(password); int result; String message = ""; // 判斷用戶是否已經存在 UserQuery findUserQuery = new UserQuery(); findUserQuery.setUserName(userName); User existUser = this.userService.findUserByName(findUserQuery); if (existUser == null) { // 插入用戶 try { result = this.userService.addUser(query); message = "success"; } catch (Exception e) { result = 0; message = "error"; e.printStackTrace(); } // 插入用戶成功後返回用戶信息 if (result == 1) { userData = this.userService.findUserByName(findUserQuery); // 生成token String token = null; // 當前用戶 User currentUser = new User(); if (userData != null) { currentUser.setUserId(userData.getUserId()); currentUser.setUserName(userData.getUserName()); currentUser.setPassword(password); token = JWTUtils.createToken(currentUser); } if (token != null) { userData.setToken(token); // 獲取token用戶信息 // Claims userDataFromToken = JWTUtils.parseToken(token, currentUser); } } } else { message = "用戶已經存在"; } response.setData(userData); response.setMsg(message); return response; } } 複製代碼
com.zz.service.UserService
Interface 用戶接口
package com.zz.service; import com.zz.entity.User; import com.zz.query.UserQuery; import java.util.List; import java.util.Map; public interface UserService { // 添加用戶 int addUser(UserQuery query); } 複製代碼
com.zz.service.impl.UserServiceImpl
用戶接口實現類
package com.zz.service.impl; import com.zz.entity.User; import com.zz.mapper.UserMapper; import com.zz.query.UserQuery; import com.zz.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; import java.util.Map; @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public int addUser(UserQuery query){ return this.userMapper.insert(query); } } 複製代碼
com.zz.mapper.UserMapper
mapper
package com.zz.mapper; import com.zz.entity.User; import com.zz.query.UserQuery; import java.util.List; public interface UserMapper { int insert(UserQuery query); } 複製代碼
resources/mapper/UserMapper.xml
先後名字必定對應
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.zz.mapper.UserMapper"> <resultMap id="BaseResult" type="com.zz.entity.User"> <id column="user_id" property="userId"></id> <id column="user_name" property="userName"></id> </resultMap> <sql id="base"> user_id, user_name <if test="showPassword"> , password </if> </sql> <sql id="base_condition"> <where> <if test="userName!=null and userName!=''"> user_name=#{userName} </if> <if test="password!=null and password!=''"> and password=#{password} </if> </where> </sql> <insert id="insert"> INSERT INTO user( user_name, password ) VALUES ( #{userName}, #{password} ) </insert> </mapper> 複製代碼
到此,整個接口書寫過程已所有完成,這就是在當前架構下寫一個接口的所有過程。
因爲咱們在配置文件裏已經配置靜態資源的路徑,因此咱們能夠在resources裏面寫一個不分離的we b實例進行訪問。
resources/static/regist.html
註冊頁面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0"> <title>註冊用戶</title> <!-- 引入樣式 --> <link rel="stylesheet" href="css/regist.css"/> <link rel="stylesheet" href="https://res.wx.qq.com/open/libs/weui/2.1.3/weui.min.css"> </head> <body> <div class="container"> <div class="page form_page js_show"> <div class="weui-form"> <div class="weui-form__text-area"> <h2 class="weui-form__title">註冊新用戶</h2> </div> <div class="weui-form__control-area"> <div class="weui-cells__group weui-cells__group_form"> <div class="weui-cells weui-cells_form"> <div class="weui-cell"> <div class="weui-cell__hd"><label class="weui-label">用戶名</label></div> <div class="weui-cell__bd"> <input id="js_input——user" class="weui-input" placeholder="請輸入要設置的用戶名"> </div> </div> <div class="weui-cell"> <div class="weui-cell__hd"><label class="weui-label">密碼</label></div> <div class="weui-cell__bd"> <input id="js_input——pwd" type="password" class="weui-input" placeholder="請輸入要設置的密碼"> </div> </div> <div class="weui-cell"> <div class="weui-cell__hd"><label class="weui-label">確認密碼</label></div> <div class="weui-cell__bd"> <input id="js_input——pwd2" type="password" class="weui-input" placeholder="請再次輸入設置的密碼" type="number" pattern="[0-9]*"> </div> </div> </div> </div> </div> <!-- <div class="weui-form__tips-area">--> <!-- <p class="weui-form__tips">--> <!-- 表單頁提示,居中對齊--> <!-- </p>--> <!-- </div>--> <div class="weui-form__opr-area"> <a class="weui-btn weui-btn_primary" href="javascript:" id="submit">肯定</a> </div> <div class="weui-form__extra-area"> <div class="weui-footer"> <!-- <p class="weui-footer__links">--> <!-- <a href="javascript:void(0);" class="weui-footer__link">底部連接文本</a>--> <!-- </p>--> <p class="weui-footer__text">Copyright © 2019 alex wong</p> </div> </div> </div> <div id="js_toast" style="display: none;"> <div class="weui-mask_transparent"></div> <div class="weui-toast"> <i class="weui-icon-success-no-circle weui-icon_toast"></i> <p class="weui-toast__content">已完成</p> </div> </div> </div> </div> </body> <script src="js/md5.js"></script> <script src="js/utils.js"></script> <script src="js/dataService.js"></script> <script type="text/javascript" src="https://res.wx.qq.com/open/libs/weuijs/1.2.1/weui.min.js"></script> <script src="js/regist.js"></script> </html> 複製代碼
static/js/dataService.js
const APIURL = '/'; window.dataService = { //GET get: (url, params = {}) => { const searchArr = []; Object.keys(params).forEach(n => { searchArr.push(`${n}=${params[n]}`); }); const searchStr = searchArr.length ? '?' + searchArr.join('&') : ''; const token = utils.getCookie('token'); return fetch(APIURL + url + searchStr, { method: 'GET', headers: { token } }).then(res => res.json()); }, //POST post: (url, params = {}) => { const formData = new FormData(); Object.keys(params).forEach(n => { formData.append(n, params[n]); }); const token = utils.getCookie('token'); return fetch(APIURL + url, { method: 'POST', headers: { token }, body: formData }).then(res => res.json()); }, // 註冊 addUser(params) { return this.post('user/add', params); }, // 登陸 login(params) { return this.post('user/login', params); }, // 用戶信息 getUserInfo(params) { return this.get('user/info', params); }, }; 複製代碼
static/js/utils.js
window.utils = { // md5 generateMd5(userName, password) { const salt = "1qaz2wsx3edc4rfv5tgb6yhn7ujm8ik9ol0p@!."; const asciStr = userName + salt + password; const asciArr = asciStr.split(''); const asciResult = []; asciArr.forEach(n => { asciResult.push(n.charCodeAt()); }); const ascireusltStr = asciResult.join(salt); return hex_md5(ascireusltStr); }, // setCookie setCookie(name, value) { var time = 2 * 60 * 60 * 1000; var exp = new Date(); exp.setTime(exp.getTime() + time); document.cookie = name + "=" + escape(value) + ";expires=" + exp.toGMTString(); }, // getCookie getCookie(name) { var arr, reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)"); if (arr = document.cookie.match(reg)) return unescape(arr[2]); else return null; } }; 複製代碼
static/js/regist.js
// 獲取相關用戶信息 const userNameInput = document.getElementById("js_input——user"); const passwordInput = document.getElementById("js_input——pwd"); const passwordConfirmInput = document.getElementById("js_input——pwd2"); const submitBtn = document.getElementById("submit"); // submit submitBtn.onclick = () => { const userName = userNameInput.value; const password = passwordInput.value; const confirmPassword = passwordConfirmInput.value; // verify if (!userName) { weui.topTips('用戶姓名不能爲空'); return; } else if (!password) { weui.topTips('用戶密碼不能爲空'); return; } else if (confirmPassword !== password) { weui.topTips('先後密碼不一致,請重試'); return; } // 加密密碼 const newPassword = utils.generateMd5(userName, password); // 註冊 dataService.addUser({ userName, password: newPassword, }).then(res => { const {code, data, msg} = res; if (!data) { weui.topTips(msg); } else { weui.topTips(`註冊成功,歡迎 ${data.userName}`); window.location.href = location.origin + '/login.html'; } }) }; 複製代碼
效果如圖:
增長一些基本的校驗
用戶密碼加密傳輸,並驗證新用戶是否已經註冊
mysql 查看下用戶表
按上面第9步驟所述,下面的添加內容,請直接添加到上述服務中,再也不所有展現代碼。
com.zz.newController.UserController
首先要判斷用戶是否存在,若是存在,返回基本信息並返回用戶憑證token
/** * 登陸 * * @param userName 用戶名 * @param password 密碼 * @return {} */ @PostMapping("/login") @PassToken public Response login(@RequestParam String userName, @RequestParam String password, Response response) { UserQuery query = new UserQuery(); query.setUserName(userName); query.setPassword(password); // 驗證用戶和密碼 try { // 判斷用戶是否已經存在 User existUser = this.userService.findUserByName(query); // 生成token String token = null; // 當前用戶 User currentUser = new User(); if (existUser != null) { currentUser.setUserId(existUser.getUserId()); currentUser.setUserName(existUser.getUserName()); currentUser.setPassword(password); // 生成用戶憑證 token = JWTUtils.createToken(currentUser); if (token != null) { existUser.setToken(token); } response.setMsg("success"); response.setData(existUser); } else { // 登陸失敗 response.setMsg("登陸失敗,請檢查用戶名和密碼"); response.setData(null); } } catch (Exception e) { response.setMsg("login failed"); response.setData(null); e.printStackTrace(); } return response; } 複製代碼
com.zz.service.UserService
package com.zz.service; import com.zz.entity.User; import com.zz.query.UserQuery; import java.util.List; import java.util.Map; public interface UserService { // 添加用戶 int addUser(UserQuery query); //查找單個用戶 User findUserById(UserQuery query); User findUserByName(UserQuery query); } 複製代碼
com.zz.service.impl.UserServiceImpl
package com.zz.service.impl; import com.zz.entity.User; import com.zz.mapper.UserMapper; import com.zz.query.UserQuery; import com.zz.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; import java.util.Map; @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public int addUser(UserQuery query){ return this.userMapper.insert(query); } @Override public User findUserById(UserQuery query) { return this.userMapper.findUserById(query); } @Override public User findUserByName(UserQuery query) { return this.userMapper.findUserByName(query); } @Override public List<User> findAllUser(UserQuery query) { return this.userMapper.findAllUser(query); } } 複製代碼
com.zz.mapper.UserMapper
package com.zz.mapper; import com.zz.entity.User; import com.zz.query.UserQuery; import java.util.List; public interface UserMapper { int insert(UserQuery query); User findUserById(UserQuery query); User findUserByName(UserQuery query); List<User> findAllUser(UserQuery query); } 複製代碼
mapper/UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.zz.mapper.UserMapper"> <resultMap id="BaseResult" type="com.zz.entity.User"> <id column="user_id" property="userId"></id> <id column="user_name" property="userName"></id> </resultMap> <sql id="base"> user_id, user_name <if test="showPassword"> , password </if> </sql> <sql id="base_condition"> <where> <if test="userName!=null and userName!=''"> user_name=#{userName} </if> <if test="password!=null and password!=''"> and password=#{password} </if> </where> </sql> <!-- 查詢全部user --> <select id="findAllUser" resultMap="BaseResult"> select <include refid="base"/> from user </select> <!-- 查詢user --> <select id="findUserById" resultMap="BaseResult"> select <include refid="base"/> from user where user_id = #{userId} </select> <select id="findUserByName" resultMap="BaseResult"> select <include refid="base"/> from user <include refid="base_condition"/> </select> <insert id="insert"> INSERT INTO user( user_name, password ) VALUES ( #{userName}, #{password} ) </insert> </mapper> 複製代碼
static/login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0"> <title>login</title> <!-- 引入樣式 --> <link rel="stylesheet" href="css/regist.css"/> <link rel="stylesheet" href="https://res.wx.qq.com/open/libs/weui/2.1.3/weui.min.css"> </head> <body> <div class="container"> <div class="page form_page js_show"> <div class="weui-form"> <div class="weui-form__text-area"> <h2 class="weui-form__title">登陸</h2> </div> <div class="weui-form__control-area"> <div class="weui-cells__group weui-cells__group_form"> <div class="weui-cells weui-cells_form"> <div class="weui-cell"> <div class="weui-cell__hd"><label class="weui-label">用戶名</label></div> <div class="weui-cell__bd"> <input id="js_input——user" class="weui-input" placeholder="請輸入用戶名"> </div> </div> <div class="weui-cell"> <div class="weui-cell__hd"><label class="weui-label">密碼</label></div> <div class="weui-cell__bd"> <input id="js_input——pwd" type="password" class="weui-input" placeholder="請輸入密碼"> </div> </div> </div> </div> </div> <!-- <div class="weui-form__tips-area">--> <!-- <p class="weui-form__tips">--> <!-- 表單頁提示,居中對齊--> <!-- </p>--> <!-- </div>--> <div class="weui-form__opr-area"> <a class="weui-btn weui-btn_primary" href="javascript:" id="submit">肯定</a> </div> <div class="weui-form__extra-area"> <div class="weui-footer"> <!-- <p class="weui-footer__links">--> <!-- <a href="javascript:void(0);" class="weui-footer__link">底部連接文本</a>--> <!-- </p>--> <p class="weui-footer__text">Copyright © 2019 alex wong</p> </div> </div> </div> <div id="js_toast" style="display: none;"> <div class="weui-mask_transparent"></div> <div class="weui-toast"> <i class="weui-icon-success-no-circle weui-icon_toast"></i> <p class="weui-toast__content">已完成</p> </div> </div> </div> </div> </body> <script src="js/md5.js"></script> <script src="js/utils.js"></script> <script src="js/dataService.js"></script> <script type="text/javascript" src="https://res.wx.qq.com/open/libs/weuijs/1.2.1/weui.min.js"></script> <script src="js/login.js"></script> </html> 複製代碼
static/js/login.js
// 獲取相關用戶信息 const userNameInput = document.getElementById("js_input——user"); const passwordInput = document.getElementById("js_input——pwd"); const submitBtn = document.getElementById("submit"); // submit submitBtn.onclick = () => { const userName = userNameInput.value; const password = passwordInput.value; // verify if (!userName) { weui.topTips('用戶姓名不能爲空'); return; } else if (!password) { weui.topTips('用戶密碼不能爲空'); return; } // 加密密碼 const newPassword = utils.generateMd5(userName, password); // 註冊 dataService.login({ userName, password: newPassword, }).then(res => { const {code, data, msg} = res; if (!data) { weui.topTips(msg); } else { weui.topTips(`登陸成功,歡迎 ${data.userName}`); utils.setCookie('token', data.token); location.href = location.origin + '/home.html'; } }) }; 複製代碼
登陸接口返回用戶憑證token,後續用來校驗用戶接口,增長安全性。
在常規的業務開發中,切記不可把接口服務暴露給任何人均可以訪問,否則別人能夠任意查看或者修改你的數據,這是很嚴重的事情。除了常規從網段IP方面限制固定客戶端IP的範圍,接口自己也要增長安全驗證,這個時候咱們就須要用到以前生成的用戶憑證token;
問題是咱們若是自定義控制,哪些接口是須要通過驗證,哪些接口是不須要經過驗證的呢?有人可能會說,直接所有驗證不就能夠了,何苦糾結。可是在真實的業務中,有些接口是不能強制校驗的,好比一些用戶分享到微信的那種接口,是不能增長驗證,不然分享的頁面沒法正常顯示。
因此咱們能夠自定義註解@PassToken, 添加這個註解的接口,就能夠不用進行token驗證了。
com.zz.common.annotation.PassToken
PassToken 註解
package com.zz.common.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; // 是否跳過token驗證 @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface PassToken { boolean required() default true; } 複製代碼
添加攔截器
com.zz.common.interceptor.AuthenticationInterceptor
在發送請求的時候,在請求頭裏面加token, 而後驗證的時候token從頭部獲取
若是沒有token, 進行無token提示;
若是存在,就用 JWT 校驗 token 是否存在,而且校驗用戶密碼是否正確。
package com.zz.common.interceptor; import com.auth0.jwt.JWT; import com.auth0.jwt.exceptions.JWTDecodeException; import com.mongodb.util.JSON; import com.zz.common.annotation.PassToken; import com.zz.common.base.BaseApplicationController; import com.zz.entity.User; import com.zz.model.Response; import com.zz.query.UserQuery; import com.zz.service.UserService; import com.zz.utils.JWTUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.configurationprocessor.json.JSONException; import org.springframework.boot.configurationprocessor.json.JSONObject; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Method; // 攔截器 public class AuthenticationInterceptor implements HandlerInterceptor { @Autowired private UserService userService; /** * response返回信息 * * @param code * @param message * @return * @throws JSONException */ public JSONObject getJsonObject(int code, String message) throws JSONException { JSONObject jsonObject = new JSONObject(); jsonObject.put("msg", message); jsonObject.put("code", code); return jsonObject; } @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception { // 從 http 請求頭中取出 token String token = BaseApplicationController.getToken(); // 若是不是映射到方法直接經過 if (!(object instanceof HandlerMethod)) { return true; } HandlerMethod handlerMethod = (HandlerMethod) object; Method method = handlerMethod.getMethod(); //檢查是否有PassToken註釋,有則跳過認證 if (method.isAnnotationPresent(PassToken.class)) { PassToken passToken = method.getAnnotation(PassToken.class); if (passToken.required()) { return true; } } // 默認執行認證 httpServletResponse.setContentType("application/json;charset=UTF-8"); if (token == null || token.equals("null")) { JSONObject jsonObject = getJsonObject(403, "無token,請從新登陸"); httpServletResponse.getWriter().write(jsonObject.toString()); return false; // throw new RuntimeException("無token,請從新登陸"); } // 獲取 token 中的 user id long userId; try { userId = BaseApplicationController.getCurrentUserId(); } catch (JWTDecodeException j) { JSONObject jsonObject = getJsonObject(500, "訪問異常, token不正確,請從新登陸"); httpServletResponse.getWriter().write(jsonObject.toString()); return false; // throw new RuntimeException("訪問異常!"); } // 驗證用戶是否存在 UserQuery query = new UserQuery(); query.setUserId(userId); query.setShowPassword(Boolean.TRUE); User user = userService.findUserById(query); if (user == null) { JSONObject jsonObject = getJsonObject(500, "用戶不存在,請從新登陸"); httpServletResponse.getWriter().write(jsonObject.toString()); return false; // throw new RuntimeException("用戶不存在,請從新登陸"); } // 驗證token是否有效 Boolean verify = JWTUtils.verify(token, user); if (!verify) { JSONObject jsonObject = getJsonObject(500, "非法訪問,請從新登陸"); httpServletResponse.getWriter().write(jsonObject.toString()); return false; // throw new RuntimeException("非法訪問!"); } return true; } } 複製代碼
下面讓咱們實例看下效果:
com.zz.newController.UserController
package com.zz.newController; import com.auth0.jwt.JWT; import com.auth0.jwt.interfaces.DecodedJWT; import com.zz.common.annotation.PassToken; import com.zz.common.base.BaseApplicationController; import com.zz.entity.User; import com.zz.model.Response; import com.zz.query.UserQuery; import com.zz.service.UserService; import com.zz.utils.JWTUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.Map; /** * 登陸 * autho: alex wong */ @RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; /* * @param userName * @param password * @return response */ @PostMapping("/add") @PassToken public Response addUser(@RequestParam String userName, @RequestParam String password, Response response) { UserQuery query = new UserQuery(); User userData = null; query.setUserName(userName); query.setPassword(password); int result; String message = ""; // 判斷用戶是否已經存在 UserQuery findUserQuery = new UserQuery(); findUserQuery.setUserName(userName); User existUser = this.userService.findUserByName(findUserQuery); if (existUser == null) { // 插入用戶 try { result = this.userService.addUser(query); message = "success"; } catch (Exception e) { result = 0; message = "error"; e.printStackTrace(); } // 插入用戶成功後返回用戶信息 if (result == 1) { userData = this.userService.findUserByName(findUserQuery); // 生成token String token = null; // 當前用戶 User currentUser = new User(); if (userData != null) { currentUser.setUserId(userData.getUserId()); currentUser.setUserName(userData.getUserName()); currentUser.setPassword(password); token = JWTUtils.createToken(currentUser); } if (token != null) { userData.setToken(token); // 獲取token用戶信息 // Claims userDataFromToken = JWTUtils.parseToken(token, currentUser); } } } else { message = "用戶已經存在"; } response.setData(userData); response.setMsg(message); return response; } /** * 登陸 * * @param userName 用戶名 * @param password 密碼 * @return {} */ @PostMapping("/login") @PassToken public Response login(@RequestParam String userName, @RequestParam String password, Response response) { UserQuery query = new UserQuery(); query.setUserName(userName); query.setPassword(password); // 驗證用戶和密碼 try { // 判斷用戶是否已經存在 User existUser = this.userService.findUserByName(query); // 生成token String token = null; // 當前用戶 User currentUser = new User(); if (existUser != null) { currentUser.setUserId(existUser.getUserId()); currentUser.setUserName(existUser.getUserName()); currentUser.setPassword(password); token = JWTUtils.createToken(currentUser); if (token != null) { existUser.setToken(token); } response.setMsg("success"); response.setData(existUser); } else { // 登陸失敗 response.setMsg("登陸失敗,請檢查用戶名和密碼"); response.setData(null); } } catch (Exception e) { response.setMsg("login failed"); response.setData(null); e.printStackTrace(); } return response; } /** * 獲取我的信息 * * @return {} */ @GetMapping("/info") public Response getUserInfo(Response response) { // 獲取token String token = BaseApplicationController.getToken(); User userData2 = BaseApplicationController.getCurrentUser(); Map<String, Object> headerData = BaseApplicationController.getHeader(); if (token != null && !token.equals("null")) { User userData = new User(); DecodedJWT claims = JWT.decode(token); userData.setUserName(claims.getClaim("userName").asString()); userData.setUserId(claims.getClaim("userId").asLong()); response.setData(userData); response.setMsg("success"); } else { response.setMsg("token不存在"); } return response; } } 複製代碼
咱們新寫了一個接口,獲取用戶信息,如上,其他代碼再也不贅述;
成功獲取用戶信息
刪除token
Token 故意改錯
到此,驗證過程完美成功;
以上過程,只是一個簡單服務的搭建,真實的服務還須要更多配置,好比XSS配置防止XSS攻擊, 多源數據等。好了,本篇文章到此爲止,若是有哪裏描述得不清楚,請多多包涵。