在分布式环境中,如何支持PC、APP(ios、android)等多端的会话共享,这也是所有公司都需要的解决方案,用传统的session方式来解决,我想已经out了,我们是否可以找一个通用的方案,比如用传统cas来实现多系统之间的sso单点登录或使用oauth的第三方登录方案? 今天给大家简单讲解一下使用spring拦截器Interceptor机制、jwt认证方式、redis分布式缓存实现sso单点登录,闲话少说,直接把步骤记录下来分享给大家:
- 引入jwt的相关jar包,在项目pom.xml中引入:
1.
2. com.auth0
3. java-jwt
4. 2.2.0
5.
- 拦截器配置:
1.
2.
3.
4.
5.
我这里简单配置了要拦截的url和过滤的url(这个根据自己项目来定)
- 编写jwt的加密或者解密工具类:
1. public class JWT {
2. private static final String SECRET = "HONGHUJWT1234567890QWERTYUIOPASDFGHJKLZXCVBNM";
4. private static final String EXP = "exp";
6. private static final String PAYLOAD = "payload";
8. //加密
9. public static String sign(T object, long maxAge) {
10. try {
11. final JWTSigner signer = new JWTSigner(SECRET);
12. final Map claims = new HashMap();
13. ObjectMapper mapper = new ObjectMapper();
14. String jsonString = mapper.writeValueAsString(object);
15. claims.put(PAYLOAD, jsonString);
16. claims.put(EXP, System.currentTimeMillis() + maxAge);
17. return signer.sign(claims);
18. } catch(Exception e) {
19. return null;
20. }
21. }
23. //解密
24. public static T unsign(String jwt, Class classT) {
25. final JWTVerifier verifier = new JWTVerifier(SECRET);
26. try {
27. final Map claims= verifier.verify(jwt);
28. if (claims.containsKey(EXP) && claims.containsKey(PAYLOAD)) {
29. String json = (String)claims.get(PAYLOAD);
30. ObjectMapper objectMapper = new ObjectMapper();
31. return objectMapper.readValue(json, classT);
33. }
34. return null;
35. } catch (Exception e) {
36. return null;
37. }
38. }
39. }
这个加密工具类是我从网上找的,如果各位要修改,可以按照自己业务修改即可。
- 创建Login.java对象,用来进行jwt的加密或者解密:
1. public class Login implements Serializable{
2. /**
3. *
4. */
5. private static final long serialVersionUID = 1899232511233819216L;
7. /**
8. * 用户id
9. */
10. private String uid;
12. /**
13. * 登录用户名
14. */
15. private String loginName;
17. /**
18. * 登录密码
19. */
20. private String password;
22. public Login(){
23. super();
24. }
26. public Login(String uid, String loginName, String password){
27. this.uid = uid;
28. this.loginName = loginName;
29. this.password = password;
30. }
32. public String getUid() {
33. return uid;
34. }
35. public void setUid(String uid) {
36. this.uid = uid;
37. }
38. public String getLoginName() {
39. return loginName;
40. }
41. public void setLoginName(String loginName) {
42. this.loginName = loginName;
43. }
44. public String getPassword() {
45. return password;
46. }
47. public void setPassword(String password) {
48. this.password = password;
49. }
52. }
- 定义RedisLogin对象,用来通过uid往redis进行user对象存储:
1. public class RedisLogin implements Serializable{
2. /**
3. *
4. */
5. private static final long serialVersionUID = 8116817810829835862L;
7. /**
8. * 用户id
9. */
10. private String uid;
12. /**
13. * jwt生成的token信息
14. */
15. private String token;
17. /**
18. * 登录或刷新应用的时间
19. */
20. private long refTime;
22. public RedisLogin(){
24. }
26. public RedisLogin(String uid, String token, long refTime){
27. this.uid = uid;
28. this.token = token;
29. this.refTime = refTime;
30. }
32. public String getUid() {
33. return uid;
34. }
35. public void setUid(String uid) {
36. this.uid = uid;
37. }
38. public String getToken() {
39. return token;
40. }
41. public void setToken(String token) {
42. this.token = token;
43. }
44. public long getRefTime() {
45. return refTime;
46. }
47. public void setRefTime(long refTime) {
48. this.refTime = refTime;
49. }
53. }
- 编写LoginInterceptor.java拦截器
1. public class LoginInterceptor implements HandlerInterceptor{
3. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
4. throws Exception {
5. PrintWriter writer = null;
6. HandlerMethod method = null;
7. try {
8. method = (HandlerMethod) handler;
9. } catch (Exception e) {
10. writer = response.getWriter();
11. ResponseVO responseVO = ResponseCode.buildEnumResponseVO(ResponseCode.REQUEST_URL_NOT_SERVICE, false);
12. responseMessage(response, writer, responseVO);
13. return false;
14. }
15. IsLogin isLogin = method.getMethodAnnotation(IsLogin.class);
16. if(null == isLogin){
17. return true;
18. }
21. response.setCharacterEncoding("utf-8");
22. String token = request.getHeader("token");
23. String uid = request.getHeader("uid");
24. //token不存在
25. if(StringUtils.isEmpty(token)) {
26. writer = response.getWriter();
27. ResponseVO responseVO = LoginResponseCode.buildEnumResponseVO(LoginResponseCode.LOGIN_TOKEN_NOT_NULL, false);
28. responseMessage(response, writer, responseVO);
29. return false;
30. }
31. if(StringUtils.isEmpty(uid)){
32. writer = response.getWriter();
33. ResponseVO responseVO = LoginResponseCode.buildEnumResponseVO(LoginResponseCode.USERID_NOT_NULL, false);
34. responseMessage(response, writer, responseVO);
35. return false;
36. }
38. Login login = JWT.unsign(token, Login.class);
39. //解密token后的loginId与用户传来的loginId判断是否一致
40. if(null == login || !StringUtils.equals(login.getUid(), uid)){
41. writer = response.getWriter();
42. ResponseVO responseVO = LoginResponseCode.buildEnumResponseVO(LoginResponseCode.USERID_NOT_UNAUTHORIZED, false);
43. responseMessage(response, writer, responseVO);
44. return false;
45. }
47. //验证登录时间
48. RedisLogin redisLogin = (RedisLogin)JedisUtils.getObject(uid);
49. if(null == redisLogin){
50. writer = response.getWriter();
51. ResponseVO responseVO = LoginResponseCode.buildEnumResponseVO(LoginResponseCode.RESPONSE_CODE_UNLOGIN_ERROR, false);
52. responseMessage(response, writer, responseVO);
53. return false;
54. }
56. if(!StringUtils.equals(token, redisLogin.getToken())){
57. writer = response.getWriter();
58. ResponseVO responseVO = LoginResponseCode.buildEnumResponseVO(LoginResponseCode.USERID_NOT_UNAUTHORIZED, false);
59. responseMessage(response, writer, responseVO);
60. return false;
61. }
62. //系统时间>有效期(说明已经超过有效期)
63. if (System.currentTimeMillis() > redisLogin.getRefTime()) {
64. writer = response.getWriter();
65. ResponseVO responseVO = LoginResponseCode.buildEnumResponseVO(LoginResponseCode.LOGIN_TIME_EXP, false);
66. responseMessage(response, writer, responseVO);
67. return false;
68. }
70. //重新刷新有效期
71. redisLogin = new RedisLogin(uid, token, System.currentTimeMillis() + 60L* 1000L* 30L);
72. JedisUtils.setObject(uid , redisLogin, 360000000);
73. return true;
74. }
76. public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
77. ModelAndView modelAndView) throws Exception {
79. }
81. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
82. throws Exception {
84. }
86. private void responseMessage(HttpServletResponse response, PrintWriter out, ResponseVO responseVO) {
87. response.setContentType("application/json; charset=utf-8");
88. JSONObject result = new JSONObject();
89. result.put("result", responseVO);
90. out.print(result);
91. out.flush();
92. out.close();
93. }
95. }
- 定义异常的LoginResponseCode
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, "登录时间超长,请重新登录");
8. // 成员变量
9. private int code; //状态码
10. private String message; //返回消息
12. // 构造方法
13. private LoginResponseCode(int code,String message) {
14. this.code = code;
15. this.message = message;
16. }
17. public int getCode() {
18. return code;
19. }
20. public void setCode(int code) {
21. this.code = code;
22. }
23. public String getMessage() {
24. return message;
25. }
26. public void setMessage(String message) {
27. this.message = message;
28. }
30. public static ResponseVO buildEnumResponseVO(LoginResponseCode responseCode, Object data) {
31. return new ResponseVO(responseCode.getCode(),responseCode.getMessage(),data);
32. }
34. public static Map buildReturnMap(LoginResponseCode responseCode, Object data) {
35. Map map = new HashMap();
36. map.put("code", responseCode.getCode());
37. map.put("message", responseCode.getMessage());
38. map.put("data", data);
39. return map;
40. }
41. }
- 编写统一sso单点登录接口:
1. @RequestMapping(value = "/login", method = RequestMethod.POST)
2. public Map 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 result =new HashMap();
29. result.put("loginToken", token);
30. result.put("userId", user.getId());
31. result.put("user", user);
33. //保存用户信息到session
34. //session.setAttribute(user.getId() + "@@" + token, user);
35. //重建用户信息
36. this.rebuildLoginUser(user.getId(), token);
37. return ResponseCode.buildReturnMap(ResponseCode.RESPONSE_CODE_LOGIN_SUCCESS, result);
38. }
40. return ResponseCode.buildReturnMap(ResponseCode.USER_LOGIN_PWD_ERROR, false);
41. }
- 测试sso单点登录:
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": "[email protected]",
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. }
到此完毕!!
用java实施的电子商务平台太少了,使用spring cloud技术构建的b2b2c电子商务平台更少,大型企业分布式互联网电子商务平台,推出PC+微信+APP+云服务的云商平台系统,其中包括B2B、B2C、C2C、O2O、新零售、直播电商等子平台。(企业架构源码可以加求球:叁五三陆二肆柒二伍玖)