1.pom.xml
<!-- jwt 庫相關依賴. --> <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.7.0</version> </dependency> <!-- 使用第三方json 解析框架--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.15</version> </dependency> <!-- 是Apache開源組織提供的用於摘要運算、編碼的包 --> <!-- https://mvnrepository.com/artifact/commons-codec/commons-codec --> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.10</version> </dependency> <!-- mobile的模塊可以區分訪問主體是手機、平板、還是PC --> <dependency> <groupId>org.springframework.mobile</groupId> <artifactId>spring-mobile-device</artifactId> </dependency>
2.application.properties
server.context-path=/jwt jwt.header= Authorization jwt.secret= mySecret #設置過期時間 jwt.expiration= 604800 server.tomcat.max-http-header-size=3145728 #logging.level.root=debug
3.Application.java
package com.guilf; import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson.support.config.FastJsonConfig; import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.web.HttpMessageConverters; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.context.annotation.Bean; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import java.util.ArrayList; import java.util.List; @SpringBootApplication @ServletComponentScan public class Application { public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(true).run(args); } /** * 1.需要定義一個convert轉換消息的對象 * 2.創建配置信息,加入配置信息:比如是否需要格式化返回的json * 3.converter中添加配置信息 * 4.convert添加到converters當中 */ @Bean public HttpMessageConverters fastJsonHttpMessageConverters() { // 1.需要定義一個convert轉換消息的對象 FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter(); //2.創建配置信息,加入配置信息:比如是否需要格式化返回的json FastJsonConfig fastJsonConfig = new FastJsonConfig(); fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat); //3.converter中添加配置信息 fastConverter.setFastJsonConfig(fastJsonConfig); /** * 設置json 返回格式和編碼方式 處理亂碼問題 */ List<MediaType> mediaTypeList=new ArrayList<>(); mediaTypeList.add(MediaType.APPLICATION_JSON_UTF8); fastConverter.setSupportedMediaTypes(mediaTypeList); //4.convert添加到converters當中 HttpMessageConverter<?> converter = fastConverter; return new HttpMessageConverters(converter); } }
4.JwtHelper.java
package com.guilf.jwt.utils; import com.guilf.jwt.domain.LoginInfo; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.beans.factory.annotation.Value; import org.springframework.mobile.device.Device; import org.springframework.stereotype.Component; import javax.xml.bind.DatatypeConverter; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * jwt及解析jwt的幫助類. * */ @Component public class JwtHelper { static final String CLAIM_KEY_USERNAME = "sub"; static final String CLAIM_KEY_AUDIENCE = "audience"; static final String CLAIM_KEY_CREATED = "created"; private static final String AUDIENCE_UNKNOWN = "unknown"; private static final String AUDIENCE_WEB = "web"; private static final String AUDIENCE_MOBILE = "mobile"; private static final String AUDIENCE_TABLET = "tablet"; @Value("${jwt.secret}") private String secret; @Value("${jwt.expiration}") private Long expiration; public String getSecret() { return secret; } public void setSecret(String secret) { this.secret = secret; } public Long getExpiration() { return expiration; } public void setExpiration(Long expiration) { this.expiration = expiration; } public Claims getClaimsFromToken(String token){ Claims claims; try { claims = Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); } catch (Exception e) { claims = null; } return claims; } /** * 生成token. * @param username 用戶名 * @param device org.springframework.mobile.device 設備判斷對象 * @return */ public String createJWT(String username, Device device) { Map<String, Object> claims = new HashMap<>(); claims.put(CLAIM_KEY_USERNAME, username); claims.put(CLAIM_KEY_AUDIENCE, generateAudience(device)); claims.put(CLAIM_KEY_CREATED, new Date()); return generateToken(claims); } private String generateToken(Map<String, Object> claims) { return Jwts.builder() .setClaims(claims) .setExpiration(generateExpirationDate()) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } /** * 生成token 時間 = 當前時間+ expiration(properties中配置的失效時間) * @return */ private Date generateExpirationDate() { return new Date(System.currentTimeMillis() + expiration * 1000); } /** * 通過spring-mobile-device 的device 檢測訪問主體. * @param device * @return */ private String generateAudience(Device device) { String audience = AUDIENCE_UNKNOWN; if (device.isNormal()) { audience = AUDIENCE_WEB;//Pc端 } else if (device.isTablet()) { audience = AUDIENCE_TABLET;//Pc端 } else if (device.isMobile()) { audience = AUDIENCE_MOBILE;//平板 } return audience; } /** * 通過token 獲取用戶名. * @param authToken * @return */ public String getUsernameFromToken(String authToken) { String username; try { final Claims claims = getClaimsFromToken(authToken); username = claims.getSubject(); } catch (Exception e) { username = null; } return username; } /** * 判斷token 失效時間是否到了 * @param token * @return */ private Boolean isTokenExpired(String token) { final Date expiration = getExpirationDateFromToken(token); return expiration.before(new Date()); } /** * 獲取設置的token 失效時間. * @param token * @return */ public Date getExpirationDateFromToken(String token) { Date expiration; try { final Claims claims = getClaimsFromToken(token); expiration = claims.getExpiration(); } catch (Exception e) { expiration = null; } return expiration; } /** * Token失效校驗. * @param token token字符串 * @param loginInfo 用戶信息 * @return */ public Boolean validateToken(String token, LoginInfo loginInfo) { //1.校驗簽名是否正確 //2.token是否過期 //...... final String username = getUsernameFromToken(token); return ( username.equals(loginInfo.getUsername()) && !isTokenExpired(token)); } }
5 MyMD5Util.java
package com.guilf.jwt.utils; import org.apache.commons.codec.binary.Hex; import org.springframework.util.StringUtils; import java.security.MessageDigest; import java.util.Random; /** * 獲取md5 加密后字符串. * */ public class MyMD5Util { /** * 普通MD5 * * @param password * @return * */ public static String MD5(String password) { MessageDigest md5 = null; try { md5 = MessageDigest.getInstance("MD5"); } catch (Exception e) { e.printStackTrace(); return ""; } char[] charArray = password.toCharArray(); byte[] byteArray = new byte[charArray.length]; for (int i = 0; i < charArray.length; i++) byteArray[i] = (byte) charArray[i]; byte[] md5Bytes = md5.digest(byteArray); StringBuffer hexValue = new StringBuffer(); for (int i = 0; i < md5Bytes.length; i++) { int val = ((int) md5Bytes[i]) & 0xff; if (val < 16) hexValue.append("0"); hexValue.append(Integer.toHexString(val)); } return hexValue.toString(); } /** * 加鹽MD5 * * @param password * @return * @author */ public static String MD5Salt(String password) { Random r = new Random(); StringBuilder sb = new StringBuilder(16); sb.append(r.nextInt(99999999)).append(r.nextInt(99999999)); int len = sb.length(); if (len < 16) { for (int i = 0; i < 16 - len; i++) { sb.append("0"); } } String salt = sb.toString(); password = md5Hex(password + salt); char[] cs = new char[48]; for (int i = 0; i < 48; i += 3) { cs[i] = password.charAt(i / 3 * 2); char c = salt.charAt(i / 3); cs[i + 1] = c; cs[i + 2] = password.charAt(i / 3 * 2 + 1); } return new String(cs); } /** * 校驗加鹽后是否和原文一致 * * @param password * @param md5 * @return * @author */ public static boolean verify(String password, String md5) { char[] cs1 = new char[32]; char[] cs2 = new char[16]; for (int i = 0; i < 32; i += 3) { cs1[i / 3 * 2] = md5.charAt(i); cs1[i / 3 * 2 + 1] = md5.charAt(i + 2); cs2[i / 3] = md5.charAt(i + 1); } String salt = new String(cs2); return md5Hex(password + salt).equals(new String(cs1)); } /** * 獲取十六進制字符串形式的MD5摘要 */ private static String md5Hex(String src) { try { MessageDigest md5 = MessageDigest.getInstance("MD5"); byte[] bs = md5.digest(src.getBytes()); return new String(new Hex().encode(bs)); } catch (Exception e) { return null; } } }
6.登錄進入 JsonWebTokenController.java
package com.hong.jwt.web; import com.alibaba.fastjson.JSONObject; import com.hong.jwt.domain.AccessToken; import com.hong.jwt.domain.LoginInfo; import com.hong.jwt.neum.UserInfoEnum; import com.hong.jwt.utils.JwtHelper; import com.hong.jwt.utils.MyMD5Util; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mobile.device.Device; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * */ @RestController @RequestMapping("/oauth") public class JsonWebTokenController { @Autowired private JwtHelper jwtHelper; @RequestMapping(value = "/token") public JSONObject getAccessToken(HttpServletResponse response, LoginInfo loginInfo, Device device) { JSONObject resultMsg = new JSONObject(); try { if (loginInfo.getClientId() == null) { resultMsg.put("errcode", 500); resultMsg.put("msg", "not is ClientId!"); return resultMsg; } //驗證碼校驗在后面章節添加 //驗證用戶名密碼 //1.驗證用戶是否存在. if (!UserInfoEnum.TEST.getUsername().equals(loginInfo.getUsername()) && !UserInfoEnum.ADMIN.getUsername().equals(loginInfo.getUsername()) && !UserInfoEnum.ZHANGSAN.getUsername().equals(loginInfo.getUsername())) { resultMsg.put("result_code", 500); resultMsg.put("msg", "no user!"); return resultMsg; } //2.用戶存在,驗證密碼. else { // 獲取用戶登錄md5加密后的密碼. String loginMD5Password = MyMD5Util.MD5(loginInfo.getPassword()); // 獲取匹配的密碼. String password = null; switch (loginInfo.getUsername()) { case "admin": password = UserInfoEnum.ADMIN.getPassword(); break; case "test": password = UserInfoEnum.TEST.getPassword(); break; case "zhangsan": password = UserInfoEnum.ZHANGSAN.getPassword(); break; } String userMD5Password = MyMD5Util.MD5(password); if (!loginMD5Password.equals(userMD5Password)) { resultMsg.put("result_code", 500); resultMsg.put("msg", "password error!"); return resultMsg; } } //拼裝accessToken String accessToken = jwtHelper.createJWT(loginInfo.getUsername(), device); //返回accessToken AccessToken accessTokenEntity = new AccessToken(); accessTokenEntity.setAccessToken(accessToken); accessTokenEntity.setExpiresIn(jwtHelper.getExpiration()); accessTokenEntity.setTokenType("bearer"); resultMsg.put("result_code", 200); resultMsg.put("msg", "success"); resultMsg.put("token", accessTokenEntity); //設置cookie Cookie cookie =new Cookie("token",accessToken); cookie.setHttpOnly(true); //注:Domain為設置Cookie的有效域,Path限制有效路徑 //1、必須是1-9、a-z、A-Z、. 、- (注意是-不是_)這幾個字符組成 //2、必須是數字或字母開頭 //3、必須是數字或字母結尾 //cookie.setDomain("jwt"); response.addCookie(cookie); return resultMsg; } catch (Exception ex) { System.out.println(ex); resultMsg.put("result_code", 500); resultMsg.put("msg", "other error!"); return resultMsg; } } }
7.在進入其他控制層的時候就會進入過濾器
JwtTokenFilter.java
package com.hong.jwt.filter; import com.alibaba.fastjson.JSONObject; import com.fasterxml.jackson.databind.ObjectMapper; import com.hong.jwt.domain.LoginInfo; import com.hong.jwt.neum.UserInfoEnum; import com.hong.jwt.utils.JwtHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * */ @WebFilter(filterName = "jwtTokenFilter", urlPatterns = {"/oauth/user"}) public class JwtTokenFilter implements Filter { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired private JwtHelper jwtHelper; @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) servletRequest; // String authToken = httpRequest.getHeader("Authorization"); //從cookie 中獲取jwt token Cookie[] cookies = httpRequest.getCookies(); String authToken =null; for (Cookie cookie : cookies) { if("token".equals(cookie.getName())){ authToken = cookie.getValue(); } } String username = jwtHelper.getUsernameFromToken(authToken); logger.info("checking authentication für user " + username); if (username != null) { // 這一步通常去數據庫獲取用戶信息 LoginInfo loginInfo =new LoginInfo(); switch (username) { case "admin": loginInfo.setId(UserInfoEnum.ADMIN.getId()); loginInfo.setUsername(UserInfoEnum.ADMIN.getUsername()); loginInfo.setPassword(UserInfoEnum.ADMIN.getPassword()); break; case "test": loginInfo.setId(UserInfoEnum.TEST.getId()); loginInfo.setUsername(UserInfoEnum.TEST.getUsername()); loginInfo.setPassword(UserInfoEnum.TEST.getPassword()); break; case "zhangsan": loginInfo.setId(UserInfoEnum.ZHANGSAN.getId()); loginInfo.setUsername(UserInfoEnum.ZHANGSAN.getUsername()); loginInfo.setPassword(UserInfoEnum.ZHANGSAN.getPassword()); break; } // 驗證 token 有效性. if (jwtHelper.validateToken(authToken,loginInfo)) { filterChain.doFilter(servletRequest,servletResponse); } // 驗證token 失敗. resultWrite((HttpServletResponse) servletResponse); return; }else{ // 驗證token 失敗. resultWrite((HttpServletResponse) servletResponse); return; } } private void resultWrite(HttpServletResponse servletResponse) throws IOException { HttpServletResponse httpResponse = servletResponse; httpResponse.setCharacterEncoding("UTF-8"); httpResponse.setContentType("application/json; charset=utf-8"); httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); ObjectMapper mapper = new ObjectMapper(); JSONObject resultMsg =new JSONObject(); resultMsg.put("result_code", 500); resultMsg.put("msg", "authentication failure!"); httpResponse.getWriter().write(mapper.writeValueAsString(resultMsg)); } @Override public void destroy() { } }
8.UserController.java
package com.hong.jwt.web; import com.alibaba.fastjson.JSONObject; import com.hong.jwt.neum.UserInfoEnum; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; /** * */ @RestController public class UserController { @PostMapping("/user") public JSONObject user(int id){ JSONObject resultMsg =new JSONObject(); switch (id) { case 1: resultMsg.put("username",UserInfoEnum.TEST.getUsername()); break; case 2: resultMsg.put("username",UserInfoEnum.ADMIN.getUsername()); break; case 3: resultMsg.put("username",UserInfoEnum.ZHANGSAN.getUsername()); break; } return resultMsg; } }
package com.hong.jwt.filter;
import com.alibaba.fastjson.JSONObject;import com.fasterxml.jackson.databind.ObjectMapper;import com.hong.jwt.domain.LoginInfo;import com.hong.jwt.neum.UserInfoEnum;import com.hong.jwt.utils.JwtHelper;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.*;import javax.servlet.annotation.WebFilter;import javax.servlet.http.Cookie;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;
/** * */@WebFilter(filterName = "jwtTokenFilter", urlPatterns = {"/oauth/user"})public class JwtTokenFilter implements Filter {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired private JwtHelper jwtHelper;
@Override public void init(FilterConfig filterConfig) throws ServletException {
}
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
// String authToken = httpRequest.getHeader("Authorization");
//從cookie 中獲取jwt token Cookie[] cookies = httpRequest.getCookies(); String authToken =null; for (Cookie cookie : cookies) { if("token".equals(cookie.getName())){ authToken = cookie.getValue(); } } String username = jwtHelper.getUsernameFromToken(authToken);
logger.info("checking authentication für user " + username);
if (username != null) {
// 這一步通常去數據庫獲取用戶信息 LoginInfo loginInfo =new LoginInfo(); switch (username) { case "admin": loginInfo.setId(UserInfoEnum.ADMIN.getId()); loginInfo.setUsername(UserInfoEnum.ADMIN.getUsername()); loginInfo.setPassword(UserInfoEnum.ADMIN.getPassword()); break; case "test": loginInfo.setId(UserInfoEnum.TEST.getId()); loginInfo.setUsername(UserInfoEnum.TEST.getUsername()); loginInfo.setPassword(UserInfoEnum.TEST.getPassword()); break; case "zhangsan": loginInfo.setId(UserInfoEnum.ZHANGSAN.getId()); loginInfo.setUsername(UserInfoEnum.ZHANGSAN.getUsername()); loginInfo.setPassword(UserInfoEnum.ZHANGSAN.getPassword()); break; }
// 驗證 token 有效性. if (jwtHelper.validateToken(authToken,loginInfo)) { filterChain.doFilter(servletRequest,servletResponse); }
// 驗證token 失敗. resultWrite((HttpServletResponse) servletResponse); return; }else{ // 驗證token 失敗. resultWrite((HttpServletResponse) servletResponse); return; } }
private void resultWrite(HttpServletResponse servletResponse) throws IOException { HttpServletResponse httpResponse = servletResponse; httpResponse.setCharacterEncoding("UTF-8"); httpResponse.setContentType("application/json; charset=utf-8"); httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
ObjectMapper mapper = new ObjectMapper(); JSONObject resultMsg =new JSONObject(); resultMsg.put("result_code", 500); resultMsg.put("msg", "authentication failure!"); httpResponse.getWriter().write(mapper.writeValueAsString(resultMsg)); }
@Override public void destroy() {
}}