何為token?【如果想直接看代碼可以往下翻】
使用基於 Token 的身份驗證方法,在服務端不需要存儲用戶的登錄記錄。大概的流程是這樣的:
1. 客戶端使用用戶名跟密碼請求登錄
2. 服務端收到請求,去驗證用戶名與密碼
3. 驗證成功后,服務端會簽發一個 Token,再把這個 Token 發送給客戶端
4. 客戶端收到 Token 以后可以把它存儲起來,比如放在 Cookie 里
5. 客戶端每次向服務端請求資源的時候需要帶着服務端簽發的 Token
6. 服務端收到請求,然后去驗證客戶端請求里面帶着的 Token,如果驗證成功,就向客戶端返回請求的數據
token優點
支持跨域訪問: Cookie是不允許垮域訪問的,這一點對Token機制是不存在的,前提是傳輸的用戶認證信息通
過HTTP頭傳輸.
無狀態(也稱:服務端可擴展行):Token機制在服務端不需要存儲session信息,因為Token 自身包含了所有登
錄用戶的信息,只需要在客戶端的cookie或本地介質存儲狀態信息.
更適用CDN: 可以通過內容分發網絡請求你服務端的所有資料(如:javascript,HTML,圖片等),而你的服
務端只要提供API即可.
去耦: 不需要綁定到一個特定的身份驗證方案。Token可以在任何地方生成,只要在你的API被調用的時候,你
可以進行Token生成調用即可.
更適用於移動應用: 當你的客戶端是一個原生平台(iOS, Android,Windows 8等)時,Cookie是不被支持的
(你需要通過Cookie容器進行處理),這時采用Token認證機制就會簡單得多。
CSRF:因為不再依賴於Cookie,所以你就不需要考慮對CSRF(跨站請求偽造)的防范。
性能: 一次網絡往返時間(通過數據庫查詢session信息)總比做一次HMACSHA256計算 的Token驗證和解析
要費時得多.
不需要為登錄頁面做特殊處理: 如果你使用Protractor 做功能測試的時候,不再需要為登錄頁面做特殊處理.
基於標准化:你的API可以采用標准化的 JSON Web Token (JWT). 這個標准已經存在多個后端庫(.NET, Ruby,
Java,Python, PHP)和多家公司的支持(如:Firebase,Google, Microsoft)
如何創建token【我這里用的是Jwt機制,有些公司用的是自己的生成方法哈】
(1)創建maven工程,引入依賴
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.6.0</version> </dependency>
(2)使用工具類JwtUtils
/**
* @author: Mr.Yang
* @create: 2020-02-13 21:19
**/
@Getter
@Setter
@ConfigurationProperties("jwt.config")
public class JwtUtils {
//簽名私鑰
private String key;
//簽名失效時間
private Long failureTime;
/**
* 設置認證token
*
* @param id 用戶登錄ID
* @param subject 用戶登錄名
* @param map 其他私有數據
* @return
*/
public String createJwt(String id, String subject, Map<String, Object> map) {
//1、設置失效時間啊
long now = System.currentTimeMillis(); //毫秒
long exp = now + failureTime;
//2、創建JwtBuilder
JwtBuilder jwtBuilder = Jwts.builder().setId(id).setSubject(subject)
.setIssuedAt(new Date())
//設置簽名防止篡改
.signWith(SignatureAlgorithm.HS256, key);
//3、根據map設置claims
for (Map.Entry<String, Object> entry : map.entrySet()) {
jwtBuilder.claim(entry.getKey(), entry.getValue());
}
jwtBuilder.setExpiration(new Date(exp));
//4、創建token
String token = jwtBuilder.compact();
return token;
}
/**
* 解析token
*
* @param token
* @return
*/
public Claims parseJwt(String token) {
Claims claims = Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody();
return claims;
}
}
1 /**
2 * @author: Mr.Yang
3 * @create: 2020-02-13 21:19
4 **/
5 @Getter
6 @Setter
7 @ConfigurationProperties("jwt.config")
8 public class JwtUtils {
9 //簽名私鑰
10 private String key;
11 //簽名失效時間
12 private Long failureTime;
13
14 /**
15 * 設置認證token
16 *
17 * @param id 用戶登錄ID
18 * @param subject 用戶登錄名
19 * @param map 其他私有數據
20 * @return
21 */
22 public String createJwt(String id, String subject, Map<String, Object> map) {
23
24 //1、設置失效時間啊
25 long now = System.currentTimeMillis(); //毫秒
26 long exp = now + failureTime;
27
28 //2、創建JwtBuilder
29 JwtBuilder jwtBuilder = Jwts.builder().setId(id).setSubject(subject)
30 .setIssuedAt(new Date())
31 //設置簽名防止篡改
32 .signWith(SignatureAlgorithm.HS256, key);
33
34 //3、根據map設置claims
35 for (Map.Entry<String, Object> entry : map.entrySet()) {
36 jwtBuilder.claim(entry.getKey(), entry.getValue());
37 }
38 jwtBuilder.setExpiration(new Date(exp));
39
40 //4、創建token
41 String token = jwtBuilder.compact();
42 return token;
43 }
44
45 /**
46 * 解析token
47 *
48 * @param token
49 * @return
50 */
51 public Claims parseJwt(String token) {
52 Claims claims = Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody();
53 return claims;
54 }
55
56 }
注意:
@ConfigurationProperties("jwt.config")需要在配置文件中配置

(3)配置JwtUtils類
1 /**
2 * 配置jwt
3 *
4 * @return
5 */
6 @Bean
7 public JwtUtils jwtUtils() {
8 return new JwtUtils();
9 }
(4)編寫登錄方法
1、編寫DAO層
/**
* @author: Mr.Yang
* @create: 2020-02-13 21:55
**/
@Repository
public interface UserDAO {
public User selectByMobileUser(String mobile);
public User selectByIdUser(String id);
}
1 /**
2 * @author: Mr.Yang
3 * @create: 2020-02-13 21:55
4 **/
5 @Repository
6 public interface UserDAO {
7
8 public User selectByMobileUser(String mobile);
9
10 public User selectByIdUser(String id);
11 }
2、編寫xml寫Sql
<select id="selectByMobileUser" parameterType="string" resultMap="userMap">
select *
from bs_user
where mobile = #{mobile}
</select>
<select id="selectByMobileUser" parameterType="string" resultMap="userMap">
select *
from bs_user
where mobile = #{mobile}
</select>
3、編寫service層
/**
* 根據mobile查詢用戶
*
* @param mobile
* @return
*/
public User selectByMobile(String mobile) {
return userDAO.selectByMobileUser(mobile);
}
/**
* 根據mobile查詢用戶
*
* @param mobile
* @return
*/
public User selectByMobile(String mobile) {
return userDAO.selectByMobileUser(mobile);
}
4、編寫controller層
/**
* 用戶登錄
* 1.通過service根據mobile查詢用戶
* 2.比較password
* 3.生成jwt信息
*
* @param loginMap
* @return
* @requestBody把請求數據封裝(前端以json方式傳)
*/
@RequestMapping(value = "/login", method = RequestMethod.POST)
public Result login(@RequestBody Map<String, String> loginMap) {
String mobile = loginMap.get("mobile");
String password = loginMap.get("password");
User user = userService.selectByMobile(mobile);
//登錄失敗
if (user == null || !user.getPassword().equals(password)) {
//既可以使用拋異常,也可使用直接返回錯誤碼(推薦)
return new Result(ResultCode.MOBILEORPASSWORDERROR);
} else {
//其他數據以map集合存放在token中
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("companyId", user.getCompanyId());
dataMap.put("companyName", user.getCompanyName());
//生成token並存入數據返回
String token = jwtUtils.createJwt(user.getId(), user.getUsername(), dataMap);
return new Result(Result.SUCCESS(), token);
}
}
1 /**
2 * 用戶登錄
3 * 1.通過service根據mobile查詢用戶
4 * 2.比較password
5 * 3.生成jwt信息
6 *
7 * @param loginMap
8 * @return
9 * @requestBody把請求數據封裝(前端以json方式傳)
10 */
11 @RequestMapping(value = "/login", method = RequestMethod.POST)
12 public Result login(@RequestBody Map<String, String> loginMap) {
13 String mobile = loginMap.get("mobile");
14 String password = loginMap.get("password");
15 User user = userService.selectByMobile(mobile);
16 //登錄失敗
17 if (user == null || !user.getPassword().equals(password)) {
18 //既可以使用拋異常,也可使用直接返回錯誤碼(推薦)
19 return new Result(ResultCode.MOBILEORPASSWORDERROR);
20 } else {
21 //其他數據以map集合存放在token中
22 Map<String, Object> dataMap = new HashMap<>();
23 dataMap.put("companyId", user.getCompanyId());
24 dataMap.put("companyName", user.getCompanyName());
25 //生成token並存入數據返回
26 String token = jwtUtils.createJwt(user.getId(), user.getUsername(), dataMap);
27 return new Result(Result.SUCCESS(), token);
28 }
29 }
5、測試

data就是返回的token,里面存有用戶ID,用戶姓名及以map形式存入的數據(這里主要看前端需要什么就存什么)
(6)用戶登錄成功之后,獲取用戶信息
/**
* 用戶登錄成功之后,獲取用戶信息
* 1.獲取用戶id
* 2.根據用戶id查詢用戶
* 3.構建返回值對象
* 4.響應
*
* @param request
* @return
* @throws Exception
*/
@RequestMapping(value = "/profile", method = RequestMethod.POST)
public Result profile(HttpServletRequest request) throws PendingException, Exception {
/**
* 從請求頭信息中獲取token數據
* 1.獲取請求頭信息:名稱=Authorization(前后端約定)
* 2.替換Bearer+空格
* 3.解析token
* 4.獲取clamis
*/
//1.獲取請求頭信息:名稱=Authorization(前后端約定)
String authorization = request.getHeader("Authorization");
if (StringUtils.isEmpty(authorization)) {
// throw new PendingException(ResCode.UNAUTHENTICATED);
//系統未捕捉到請求頭信息
throw new CommonException(ResultCode.UNAUTHENTICATED);
}
//2.替換Bearer+空格
String token = authorization.replace("Bearer ", "");
//3.解析token
Claims claims = jwtUtils.parseJwt(token);
//4.獲取clamis
String userId = claims.getId();
// String userId = "U01";
User user = userService.selectByIdUser(userId);
/**此處只是為了獲取token中的用戶數據,所有只簡單返回用戶對象,
* 工作則按實際要求多表查詢需要數據(根據用戶ID查詢權限)
*/
return new Result(ResultCode.SUCCESS, user);
}
1 /**
2 * 用戶登錄成功之后,獲取用戶信息
3 * 1.獲取用戶id
4 * 2.根據用戶id查詢用戶
5 * 3.構建返回值對象
6 * 4.響應
7 *
8 * @param request
9 * @return
10 * @throws Exception
11 */
12 @RequestMapping(value = "/profile", method = RequestMethod.POST)
13 public Result profile(HttpServletRequest request) throws PendingException, Exception {
14
15 /**
16 * 從請求頭信息中獲取token數據
17 * 1.獲取請求頭信息:名稱=Authorization(前后端約定)
18 * 2.替換Bearer+空格
19 * 3.解析token
20 * 4.獲取clamis
21 */
22
23
24 //1.獲取請求頭信息:名稱=Authorization(前后端約定)
25 String authorization = request.getHeader("Authorization");
26 if (StringUtils.isEmpty(authorization)) {
27 // throw new PendingException(ResCode.UNAUTHENTICATED);
28 //系統未捕捉到請求頭信息
29 throw new CommonException(ResultCode.UNAUTHENTICATED);
30 }
31 //2.替換Bearer+空格
32 String token = authorization.replace("Bearer ", "");
33
34 //3.解析token
35 Claims claims = jwtUtils.parseJwt(token);
36 //4.獲取clamis
37 String userId = claims.getId();
38
39 // String userId = "U01";
40 User user = userService.selectByIdUser(userId);
41
42 /**此處只是為了獲取token中的用戶數據,所有只簡單返回用戶對象,
43 * 工作則按實際要求多表查詢需要數據(根據用戶ID查詢權限)
44 */
45
46 return new Result(ResultCode.SUCCESS, user);
47 }
(7)測試

先攔截所有需要被過濾的HTTP請求,再獲取HTTP的head中攜帶的token字符串,在對其進行解析。
如何攔截固定的HTTP請求可以點擊:這里
代碼的主要邏輯是:
首先攔截http請求,獲取head中的token,解析token,得到token的狀態碼code,然后在httpServletResponse的頭中添加code后,繼續執行代碼,到Controller的方法體中,獲取response中的code並根據code進行判斷當前token的狀態,進行相對應的處理。其他情況也是類似。
@WebFilter(urlPatterns = {"/app/*"})
public class TokenAuthorFilter implements Filter {
private static Logger logger = LoggerFactory.getLogger(TokenAuthorFilter.class);
@Autowired
private JWTService jwtService;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse rep = (HttpServletResponse) response;
//設置允許跨域的配置
// 這里填寫你允許進行跨域的主機ip(正式上線時可以動態配置具體允許的域名和IP)
rep.setHeader("Access-Control-Allow-Origin", "*");
// 允許的訪問方法
rep.setHeader("Access-Control-Allow-Methods","POST, GET");
// Access-Control-Max-Age 用於 CORS 相關配置的緩存
// rep.setHeader("Access-Control-Max-Age", "3600");
rep.setHeader("Access-Control-Allow-Headers","token");
// rep.setHeader("Access-Control-Allow-Headers","token,Origin, X-Requested-With, Content-Type, Accept");
rep.setCharacterEncoding("UTF-8");
rep.setContentType("application/json; charset=utf-8");
String token = req.getHeader("token");//header方式
String method = ((HttpServletRequest) request).getMethod();
if (method.equals("OPTIONS")) {
rep.setStatus(HttpServletResponse.SC_OK);
}else{
if (null == token || token.isEmpty()) {
logger.info("用戶授權認證沒有通過!客戶端請求參數中無token信息");
rep.setHeader("code","100");
rep.setHeader("msg","用戶授權認證沒有通過!客戶端請求參數中無token信息");
chain.doFilter(req, rep);
} else {
String exception=JavaWebTokenUtil.parseJWT(token).toString();
if (exception!=null){
if (ExpiredJwtException.class.getName().equals(exception)){
logger.info("token已過期!");
rep.setHeader("code","100");
rep.setHeader("msg","token is overtime!");
chain.doFilter(req, rep);
}else if (SignatureException.class.getName().equals(exception)){
System.out.println("token sign解析失敗");
rep.setHeader("code","100");
rep.setHeader("msg","token is invalid! ");
chain.doFilter(req, rep);
}else if (MalformedJwtException.class.getName().equals(exception)){
System.out.println("token的head解析失敗");
rep.setHeader("code","100");
rep.setHeader("msg","token is invalid! ");
chain.doFilter(req, rep);
}else if ("success".equals(exception)){
logger.info("用戶授權認證通過!");
rep.setHeader("code","0");
chain.doFilter(req, rep);
}else {
logger.info("token的有效期小與2天!");
String newToken=jwtService.updateToken(exception);
logger.info("已生成新的token:"+newToken);
rep.setHeader("code","1");
// rep.setHeader("msg","token will invalid at 2 days!please refresh");
rep.setHeader("newToken",newToken);
chain.doFilter(req, rep);
}
}
}
}
}
@Override
public void destroy() {
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
logger.info("初始化filter");
}
}
Controller中的代碼:
@ResponseBody
@GetMapping("app/refresh")
public Object refresh(HttpServletRequest request, HttpServletResponse response) {
Result result=appControllerService.codeFilter(response);
System.out.println(result.getCode());
System.out.println(result.getMsg());
Token token=(Token) result.getData();
if (token!=null){
System.out.println(token.getToken());
}
return result;
}
public Result codeFilter(HttpServletResponse response){
String code = response.getHeader("code");
logger.info("code:" + code);
// String msg = response.getHeader("msg");
// logger.info(msg);
if (code.equals("0")) { //如果code為空,則說明對token認證成功
return ResultUtil.success1();
} else if (code.equals("1")) { //code是1 表示:token解析成功,但是token即將過期,需要更換新的token
String newToken = response.getHeader("newToken");
//把更新的token保存到數據庫的user表中。
Integer id=tokenUtil.getCurrentUserId(newToken);
User user1=new User();
user1=userRepo.findOne(id);
System.out.println(id);
user1.setId(id);
user1.setToken(newToken);
userRepo.save(user1);
logger.info("token已經被替換!");
Token token = new Token();
token.setToken(newToken);
return ResultUtil.error(1, "success", token);
} else if (code.equals("100")) { //code是100 表示: token解析失敗
return ResultUtil.error(100, "error", null);
} else {
return ResultUtil.error(103, "error", null);
}
}

