spring+springmvc+Interceptor+jwt+redis實現sso單點登陸

在分佈式環境中,如何支持PC、APP(ios、android)等多端的會話共享,這也是全部公司都須要的解決方案,用傳統的session方式來解決,我想已經out了,咱們是否能夠找一個通用的方案,好比用傳統cas來實現多系統之間的sso單點登陸或使用oauth的第三方登陸方案? 今天給你們簡單講解一下使用spring攔截器Interceptor機制、jwt認證方式、redis分佈式緩存實現sso單點登陸,閒話少說,直接把步驟記錄下來分享給你們:java

1. 引入jwt的相關jar包,在項目pom.xml中引入:android

Java代碼 收藏代碼
  1. <dependency>
  2. <groupId>com.auth0</groupId>
  3. <artifactId>java-jwt</artifactId>
  4. <version>2.2.0</version>
  5. </dependency>

2. 攔截器配置:ios

Java代碼 收藏代碼
  1. <mvc:interceptor>
  2. <mvc:mapping path="${adminPath}/**" />
  3. <mvc:exclude-mapping path="${adminPath}/rest/login"/>
  4. <bean class="com.ml.honghu.interceptor.LoginInterceptor" />
  5. </mvc:interceptor>

我這裏簡單配置了要攔截的url和過濾的url(這個根據本身項目來定)redis

3. 編寫jwt的加密或者解密工具類:spring

Java代碼 收藏代碼
  1. public class JWT {
  2. private static final String SECRET = "HONGHUJWT1234567890QWERTYUIOPASDFGHJKLZXCVBNM";
  3. private static final String EXP = "exp";
  4. private static final String PAYLOAD = "payload";
  5. //加密
  6. public static <T> String sign(T object, long maxAge) {
  7. try {
  8. final JWTSigner signer = new JWTSigner(SECRET);
  9. final Map<String, Object> claims = new HashMap<String, Object>();
  10. ObjectMapper mapper = new ObjectMapper();
  11. String jsonString = mapper.writeValueAsString(object);
  12. claims.put(PAYLOAD, jsonString);
  13. claims.put(EXP, System.currentTimeMillis() + maxAge);
  14. return signer.sign(claims);
  15. } catch(Exception e) {
  16. return null;
  17. }
  18. }
  19. //解密
  20. public static<T> T unsign(String jwt, Class<T> classT) {
  21. final JWTVerifier verifier = new JWTVerifier(SECRET);
  22. try {
  23. final Map<String,Object> claims= verifier.verify(jwt);
  24. if (claims.containsKey(EXP) && claims.containsKey(PAYLOAD)) {
  25. String json = (String)claims.get(PAYLOAD);
  26. ObjectMapper objectMapper = new ObjectMapper();
  27. return objectMapper.readValue(json, classT);
  28. }
  29. return null;
  30. } catch (Exception e) {
  31. return null;
  32. }
  33. }
  34. }

這個加密工具類是我從網上找的,若是各位要修改,能夠按照本身業務修改便可。數據庫

4. 建立Login.java對象,用來進行jwt的加密或者解密:json

Java代碼 收藏代碼
  1. public class Login implements Serializable{
  2. /**
  3. *
  4. */
  5. private static final long serialVersionUID = 1899232511233819216L;
  6. /**
  7. * 用戶id
  8. */
  9. private String uid;
  10. /**
  11. * 登陸用戶名
  12. */
  13. private String loginName;
  14. /**
  15. * 登陸密碼
  16. */
  17. private String password;
  18. public Login(){
  19. super();
  20. }
  21. public Login(String uid, String loginName, String password){
  22. this.uid = uid;
  23. this.loginName = loginName;
  24. this.password = password;
  25. }
  26. public String getUid() {
  27. return uid;
  28. }
  29. public void setUid(String uid) {
  30. this.uid = uid;
  31. }
  32. public String getLoginName() {
  33. return loginName;
  34. }
  35. public void setLoginName(String loginName) {
  36. this.loginName = loginName;
  37. }
  38. public String getPassword() {
  39. return password;
  40. }
  41. public void setPassword(String password) {
  42. this.password = password;
  43. }
  44. }

5. 定義RedisLogin對象,用來經過uid往redis進行user對象存儲:緩存

Java代碼 收藏代碼
  1. public class RedisLogin implements Serializable{
  2. /**
  3. *
  4. */
  5. private static final long serialVersionUID = 8116817810829835862L;
  6. /**
  7. * 用戶id
  8. */
  9. private String uid;
  10. /**
  11. * jwt生成的token信息
  12. */
  13. private String token;
  14. /**
  15. * 登陸或刷新應用的時間
  16. */
  17. private long refTime;
  18. public RedisLogin(){
  19. }
  20. public RedisLogin(String uid, String token, long refTime){
  21. this.uid = uid;
  22. this.token = token;
  23. this.refTime = refTime;
  24. }
  25. public String getUid() {
  26. return uid;
  27. }
  28. public void setUid(String uid) {
  29. this.uid = uid;
  30. }
  31. public String getToken() {
  32. return token;
  33. }
  34. public void setToken(String token) {
  35. this.token = token;
  36. }
  37. public long getRefTime() {
  38. return refTime;
  39. }
  40. public void setRefTime(long refTime) {
  41. this.refTime = refTime;
  42. }
  43. }

6. 編寫LoginInterceptor.java攔截器session

Java代碼 收藏代碼
  1. public class LoginInterceptor implements HandlerInterceptor{
  2. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
  3. throws Exception {
  4. PrintWriter writer = null;
  5. HandlerMethod method = null;
  6. try {
  7. method = (HandlerMethod) handler;
  8. } catch (Exception e) {
  9. writer = response.getWriter();
  10. ResponseVO responseVO = ResponseCode.buildEnumResponseVO(ResponseCode.REQUEST_URL_NOT_SERVICE, false);
  11. responseMessage(response, writer, responseVO);
  12. return false;
  13. }
  14. IsLogin isLogin = method.getMethodAnnotation(IsLogin.class);
  15. if(null == isLogin){
  16. return true;
  17. }
  18. response.setCharacterEncoding("utf-8");
  19. String token = request.getHeader("token");
  20. String uid = request.getHeader("uid");
  21. //token不存在
  22. if(StringUtils.isEmpty(token)) {
  23. writer = response.getWriter();
  24. ResponseVO responseVO = LoginResponseCode.buildEnumResponseVO(LoginResponseCode.LOGIN_TOKEN_NOT_NULL, false);
  25. responseMessage(response, writer, responseVO);
  26. return false;
  27. }
  28. if(StringUtils.isEmpty(uid)){
  29. writer = response.getWriter();
  30. ResponseVO responseVO = LoginResponseCode.buildEnumResponseVO(LoginResponseCode.USERID_NOT_NULL, false);
  31. responseMessage(response, writer, responseVO);
  32. return false;
  33. }
  34. Login login = JWT.unsign(token, Login.class);
  35. //解密token後的loginId與用戶傳來的loginId判斷是否一致
  36. if(null == login || !StringUtils.equals(login.getUid(), uid)){
  37. writer = response.getWriter();
  38. ResponseVO responseVO = LoginResponseCode.buildEnumResponseVO(LoginResponseCode.USERID_NOT_UNAUTHORIZED, false);
  39. responseMessage(response, writer, responseVO);
  40. return false;
  41. }
  42. //驗證登陸時間
  43. RedisLogin redisLogin = (RedisLogin)JedisUtils.getObject(uid);
  44. if(null == redisLogin){
  45. writer = response.getWriter();
  46. ResponseVO responseVO = LoginResponseCode.buildEnumResponseVO(LoginResponseCode.RESPONSE_CODE_UNLOGIN_ERROR, false);
  47. responseMessage(response, writer, responseVO);
  48. return false;
  49. }
  50. if(!StringUtils.equals(token, redisLogin.getToken())){
  51. writer = response.getWriter();
  52. ResponseVO responseVO = LoginResponseCode.buildEnumResponseVO(LoginResponseCode.USERID_NOT_UNAUTHORIZED, false);
  53. responseMessage(response, writer, responseVO);
  54. return false;
  55. }
  56. //系統時間>有效期(說明已經超過有效期)
  57. if (System.currentTimeMillis() > redisLogin.getRefTime()) {
  58. writer = response.getWriter();
  59. ResponseVO responseVO = LoginResponseCode.buildEnumResponseVO(LoginResponseCode.LOGIN_TIME_EXP, false);
  60. responseMessage(response, writer, responseVO);
  61. return false;
  62. }
  63. //從新刷新有效期
  64. redisLogin = new RedisLogin(uid, token, System.currentTimeMillis() + 60L* 1000L* 30L);
  65. JedisUtils.setObject(uid , redisLogin, 360000000);
  66. return true;
  67. }
  68. public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
  69. ModelAndView modelAndView) throws Exception {
  70. }
  71. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
  72. throws Exception {
  73. }
  74. private void responseMessage(HttpServletResponse response, PrintWriter out, ResponseVO responseVO) {
  75. response.setContentType("application/json; charset=utf-8");
  76. JSONObject result = new JSONObject();
  77. result.put("result", responseVO);
  78. out.print(result);
  79. out.flush();
  80. out.close();
  81. }
  82. }

7. 定義異常的LoginResponseCodemvc

Java代碼 收藏代碼
  1. public enum LoginResponseCode {
  2. USERID_NOT_NULL(3001,"用戶id不能爲空."),
  3. LOGIN_TOKEN_NOT_NULL(3002,"登陸token不能爲空."),
  4. USERID_NOT_UNAUTHORIZED(3003, "用戶token或ID驗證不經過"),
  5. RESPONSE_CODE_UNLOGIN_ERROR(421, "未登陸異常"),
  6. LOGIN_TIME_EXP(3004, "登陸時間超長,請從新登陸");
  7. // 成員變量
  8. private int code; //狀態碼
  9. private String message; //返回消息
  10. // 構造方法
  11. private LoginResponseCode(int code,String message) {
  12. this.code = code;
  13. this.message = message;
  14. }
  15. public int getCode() {
  16. return code;
  17. }
  18. public void setCode(int code) {
  19. this.code = code;
  20. }
  21. public String getMessage() {
  22. return message;
  23. }
  24. public void setMessage(String message) {
  25. this.message = message;
  26. }
  27. public static ResponseVO buildEnumResponseVO(LoginResponseCode responseCode, Object data) {
  28. return new ResponseVO(responseCode.getCode(),responseCode.getMessage(),data);
  29. }
  30. public static Map<String, Object> buildReturnMap(LoginResponseCode responseCode, Object data) {
  31. Map<String, Object> map = new HashMap<String, Object>();
  32. map.put("code", responseCode.getCode());
  33. map.put("message", responseCode.getMessage());
  34. map.put("data", data);
  35. return map;
  36. }
  37. }

8. 編寫統一sso單點登陸接口:

Java代碼 收藏代碼
  1. @RequestMapping(value = "/login", method = RequestMethod.POST)
  2. public Map<String, Object> login(@RequestBody JSONObject json){
  3. String loginName = json.optString("loginName");
  4. String password = json.optString("password");
  5. //校驗用戶名不能爲空
  6. if(StringUtils.isEmpty(loginName)){
  7. return MemberResponseCode.buildReturnMap(MemberResponseCode.RESPONSE_CODE_USER_NAME_IS_NOT_EMPTY, null);
  8. }
  9. //校驗用戶密碼不能爲空
  10. if(StringUtils.isEmpty(password)){
  11. return MemberResponseCode.buildReturnMap(MemberResponseCode.RESPONSE_CODE_PWD_CAN_NOT_BE_EMPTY, null);
  12. }
  13. //根據用戶名查詢數據庫用戶信息
  14. User user = systemService.getBaseUserByLoginName(loginName);
  15. //用戶名或密碼不正確
  16. if(null == user){
  17. return MemberResponseCode.buildReturnMap(MemberResponseCode.RESPONSE_CODE_USER_VALIDATE_NO_SUCCESS, false);
  18. }
  19. boolean isValidate = systemService.validatePassword(password, user.getPassword());
  20. if(!isValidate){
  21. return MemberResponseCode.buildReturnMap(MemberResponseCode.RESPONSE_CODE_USER_VALIDATE_NO_SUCCESS, false);
  22. }
  23. if(isValidate){
  24. //HttpSession session =request.getSession(false);
  25. Login login = new Login(user.getId(), user.getLoginName(), user.getPassword());
  26. //給用戶jwt加密生成token
  27. String token = JWT.sign(login, 60L* 1000L* 30L);
  28. Map<String,Object> result =new HashMap<String,Object>();
  29. result.put("loginToken", token);
  30. result.put("userId", user.getId());
  31. result.put("user", user);
  32. //保存用戶信息到session
  33. //session.setAttribute(user.getId() + "@@" + token, user);
  34. //重建用戶信息
  35. this.rebuildLoginUser(user.getId(), token);
  36. return ResponseCode.buildReturnMap(ResponseCode.RESPONSE_CODE_LOGIN_SUCCESS, result);
  37. }
  38. return ResponseCode.buildReturnMap(ResponseCode.USER_LOGIN_PWD_ERROR, false);
  39. }

9. 測試sso單點登陸:



返回結果集:

Java代碼 收藏代碼
  1. {
  2. "message": "用戶登陸成功",
  3. "data": {
  4. "loginToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MDkzODA1OTU0NTksInBheWxvYWQiOiJ7XCJ1aWRcIjpcIjExXCIsXCJsb2dpbk5hbWVcIjpcImFkbWluXCIsXCJwYXNzd29yZFwiOlwiZjU0NGQxM2QyY2EwNDU5ZGQ0ZTU1NzVjNmZkYWIzMzM0MzE1MWFlZjgwYmE5ZTNiN2U1ZjM2MzJcIn0ifQ.56L60WtxHXSu9vNs6XsWy5zbmc3kP_IWG1YpReK50DM",
  5. "userId": "11",
  6. "user": {
  7. "QQ":"2147775633",
  8. "id": "11",
  9. "isNewRecord": false,
  10. "remarks": "",
  11. "createDate": "2017-08-08 08:08:08",
  12. "updateDate": "2017-10-29 11:23:50",
  13. "loginName": "admin",
  14. "no": "00012",
  15. "name": "admin",
  16. "email": "2147775633@qq.com",
  17. "phone": "400000000",
  18. "mobile": "13888888888",
  19. "userType": "",
  20. "loginIp": "0:0:0:0:0:0:0:1",
  21. "loginDate": "2017-10-30 10:48:06",
  22. "loginFlag": "1",
  23. "photo": "",
  24. "idCard": "420888888888888888",
  25. "oldLoginIp": "0:0:0:0:0:0:0:1",
  26. "oldLoginDate": "2017-10-30 10:48:06",
  27. "roleNames": "",
  28. "admin": false
  29. }
  30. },
  31. "code": 200
  32. }

到此完畢!!

相關文章
相關標籤/搜索