本文实现的方式为:cookie+jwt+redis,先来一张图
现在我以传统的springmvc+jsp的方式来演示整个运转流程,首先创建认证中心
//认证中心的Controller,地址为:http://sso.fg.cn:8080/sso/
@Controller
public class SSOController {
@Autowired
HttpServletRequest request;
//to 登录
@RequestMapping("/index")
public String index(){
String redirectUrl = request.getParameter("redirectUrl"); //获取登录后重定向的url
request.setAttribute("redirectUrl",redirectUrl);
return "index"; //渲染登录页
}
// do 登录
@RequestMapping("/login")
@ResponseBody
public String login(String username,String password){
//模拟登录成功
if ("admin".equals(username) && "admin".equals(password)) {
//存入redis,key=user:admin:info value=admin,过期时间1个小时
Jedis jedis = new Jedis("192.168.1.3",6379);
jedis.setex("user:admin:info", 3600, "admin");
jedis.close();
//使用jwt生成token
Map map = new HashMap();
map.put("username", "admin");
System.out.println(GenericUtil.getIpAddr(request));
return JwtUtil.encode("sso.fg.cn", map, "salt");
}
return "fail";
}
// 认证方法,校验真实性
@RequestMapping("/verify")
@ResponseBody
public String verify(){
String token = request.getParameter("token"); //获取token
if(token != null){
Map map = JwtUtil.decode(token, "sso.fg.cn", "salt");
if (map != null && map.size() > 0) { //解密成功
String username = (String) map.get("username");
//查看redis中是否存在该token
Jedis jedis = new Jedis("192.168.1.3",6379);
String userinfo = jedis.get("user:" + username + ":info");
jedis.expire("user:" + username + ":info", 3600); //重新设置过期时间
jedis.close();
if(!StringUtils.isEmpty(userinfo)){ //不为空则返回成功
return "success";
}
}
}
return "fail"; //为空说明要么token是伪造的,要么过期了,返回失败
}
}
//关于jwt这里不做介绍
public class JwtUtil {
/**
* 加密
* key:秘钥
* param:存放用户信息
* salt:盐值
*/
public static String encode(String key, Map param, String salt) {
if (salt != null) {
key += salt;
}
String token = null;
try {
token = Jwts.builder().signWith(SignatureAlgorithm.HS256, key.getBytes("utf-8")).setClaims(param).compact();
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} //制作token
return token;
}
//解密,返回用户信息
public static Map decode(String token , String key, String salt) {
Claims claims=null;
if (salt!=null){
key+=salt;
}
try {
claims= Jwts.parser().setSigningKey(key.getBytes("utf-8")).parseClaimsJws(token).getBody();
} catch ( Exception e) {
return null;
}
return claims;
}
}
认证中心创建完毕,现在创建web01
package cn.fg.controller;
import java.io.UnsupportedEncodingException;
import java.util.Map;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import com.alibaba.fastjson.JSON;
import io.jsonwebtoken.impl.Base64UrlCodec;
//web01的Controller,地址为:http://web01.fg.cn:8081/web01/
@Controller
public class Web01Controller {
@Autowired
HttpServletRequest request;
@Autowired
HttpServletResponse response;
//通过认证中心登录页,成功登录后的重定向地址
@RequestMapping("/index")
public String index(String newToken) {
//newToken表示是从认证中心的登录页面过来的
if(newToken != null){
//直接从token串中拿出用户数据,考虑更安全的话需要调用认证中心的认证方法进行校验
Map map = this.getUserinfoByToken(newToken);
if (map != null && map.size() != 0) { // 成功取出用户信息
request.setAttribute("userinfo", map); // 用来页面显示用户信息
// 设置cookie以便其他模块单点登陆
Cookie cookie = new Cookie("token", newToken);
cookie.setHttpOnly(true);
cookie.setPath("/");
cookie.setDomain(".fg.cn");
response.addCookie(cookie);
}
}
return "index";
}
private Map getUserinfoByToken(String token) {
// eyJhbGciOiJIUzI1NiJ9.eyJuaWNrTmFtZSI6Im1hcnJ5IiwidXNlcklkIjoiMTAwMSJ9.TF1RTg_1TnkPNOAkA4Gq549iqwzsBplgeabpHvW15ng
String tokenUserInfo = org.apache.commons.lang3.StringUtils.substringBetween(token, "."); //两点之间的就是用户信息
io.jsonwebtoken.impl.Base64UrlCodec base64UrlCodec = new Base64UrlCodec();
byte[] bytes = base64UrlCodec.decode(tokenUserInfo);
String str = null;
try {
str = new String(bytes ,"UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return JSON.parseObject(str, Map.class);
}
}
web01
登录 |
您好,${userinfo.username },欢迎登陆,
我的订单
web01创建完毕,现在创建web02站点
package cn.fg.controller;
import java.io.IOException;
import java.util.Map;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.http.Consts;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.fluent.Request;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import cn.fg.util.JwtUtil;
//web02的Controller 地址:http://web02.fg.cn:8082/web02/
@Controller
public class Web02Controller {
@Autowired
HttpServletRequest request;
@Autowired
HttpServletResponse response;
//to 我的订单
@RequestMapping("/myOrder")
public String myOrder() throws ClientProtocolException, IOException {
//查看cookie中是否有cookie,有则说明有其他系统登录过了
String token = null;
Cookie[] cookies = request.getCookies();
if(cookies != null){
for (Cookie cookie : cookies) {
if("token".equals(cookie.getName())){
token = cookie.getValue();
break;
}
}
}
if(token == null){
token = request.getParameter("newToken"); //如果存在newToken说明是登录页面过来的
}
if(token != null){
//我认为该功能比较重要,需要调用认证中心进行校验
String result = Request.Get("http://sso.fg.cn:8080/sso/verify.do?token=" + token).execute().returnContent().asString(Consts.UTF_8);
if ("success".equals(result)) {
Map userinfo = JwtUtil.decode(token); //使用jwt解密
//设置cookie以便其他模块单点登陆
Cookie cookie = new Cookie("token", token);
cookie.setHttpOnly(true);
cookie.setPath("/");
cookie.setDomain(".fg.cn");
response.addCookie(cookie);
request.setAttribute("userinfo", userinfo); //显示用户信息
return "order"; //渲染订单页
}
}
//没拿到token,去登录
return "redirect:http://sso.fg.cn:8080/sso/index.do?redirectUrl=" + request.getRequestURL().toString();
}
}
web02
您好,${userinfo.username },欢迎访问订单系统。
到此代码创建完成,我们来看看效果
以上只是一个演示,提供思路,在实际开发中,cookie、token的校验应该用拦截器来实现,通过自定义注解,来确定请求该方法前需不要登录
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequire {
// 默认设置
boolean autoRedirect() default true;
}
public class AuthInterceptor extends HandlerInterceptorAdapter {
// preHandle 进入控制器之前。
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 将生产token 放入cookie 中
// http://item.gmall.com/32.html?newToken=eyJhbGciOiJIUzI1NiJ9.eyJuaWNrTmFtZSI6IkFkbWluaXN0cmF0b3IiLCJ1c2VySWQiOiIyIn0.WUvbFvXQnTMBGNyHWT-DE41MR9cn7c_W1oAtDAzb7VU
String token = request.getParameter("newToken");
if (token!=null){
// 将token 放入cookie 中
CookieUtil.setCookie(request,response,"token",token,WebConst.COOKIE_MAXAGE,false);
}
// 直接访问登录页面,当用户进入其他项目模块中。
if (token==null){
// 如果用户登录了,访问其他页面的时候不会有newToken,那么token 可能已经在cookie 中存在了
token = CookieUtil.getCookieValue(request,"token",false);
}
// 已经登录的token,cookie 中的token。
if (token!=null){
// 去token 中的是有效数据,解密
Map map = getUserMapByToken(token);
String nickName = (String) map.get("nickName");
request.setAttribute("nickName", nickName);
}
// Object handler
// 获取方法,获取方法上的注解
HandlerMethod handlerMethod = (HandlerMethod) handler;
LoginRequire methodAnnotation = handlerMethod.getMethodAnnotation(LoginRequire.class);
// 说明类上有注解!
if (methodAnnotation!=null){
// 必须要登录【调用认证】
// 认证控制器在那个项目? 远程调用,
String result = HttpClientUtil.doGet("http://sso.fg.cn:8080/sso/verify.do?token=" + token);
if ("success".equals(result)){
// 说明当前用户已经登录,保存userId : 购物车使用!
Map map = getUserinfoByToken(token);
String username = (String) map.get("admin");
request.setAttribute("username", username);
return true;
} else {
// fail
if (methodAnnotation.autoRedirect()){
// 认证失败!重新登录!
String requestURL = request.getRequestURL().toString();
// 进行加密编码
String encodeURL = URLEncoder.encode(requestURL, "UTF-8");
response.sendRedirect("http://sso.fg.cn:8080/sso/index.do?redirectUrl=" + request.getRequestURL().toString());
return false;
}
}
}
return true;
}
本案例cookie的设置是放在应用系统上,也可以考虑放在认证中心那边,少写点代码。但是要一定是在同一顶级域名下
package cn.fg.util;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
//Cookie工具类
public class CookieUtil {
public static String getCookieValue(HttpServletRequest request, String cookieName, boolean isDecoder) {
Cookie[] cookies = request.getCookies();
if (cookies == null || cookieName == null){
return null;
}
String retValue = null;
try {
for (int i = 0; i < cookies.length; i++) {
if (cookies[i].getName().equals(cookieName)) {
if (isDecoder) {//如果涉及中文
retValue = URLDecoder.decode(cookies[i].getValue(), "UTF-8");
} else {
retValue = cookies[i].getValue();
}
break;
}
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return retValue;
}
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, int cookieMaxage, boolean isEncode) {
try {
if (cookieValue == null) {
cookieValue = "";
} else if (isEncode) {
cookieValue = URLEncoder.encode(cookieValue, "utf-8");
}
Cookie cookie = new Cookie(cookieName, cookieValue);
if (cookieMaxage >= 0)
cookie.setMaxAge(cookieMaxage);
if (null != request)// 设置域名的cookie
// 访问的域名 gmall.com
cookie.setDomain(getDomainName(request));
/**
* WebContent
* index.jsp
* cookie.setDomain("localhost"); // 设置cookie的作用域
* localhsot:8080/webTest/index.jsp 成功!
* 127.0.0.1:8080/webTest/index.jsp 访问失败!
*
*/
// 当前根目录
cookie.setPath("/");
/**
* WebContent
* index.jsp
* index/index1.jsp
* cookie.setPath("/index");
*/
response.addCookie(cookie);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 得到cookie的域名
*/
private static final String getDomainName(HttpServletRequest request) {
String domainName = null;
String serverName = request.getRequestURL().toString();
if (serverName == null || serverName.equals("")) {
domainName = "";
} else {
serverName = serverName.toLowerCase();
serverName = serverName.substring(7);
final int end = serverName.indexOf("/");
serverName = serverName.substring(0, end);
final String[] domains = serverName.split("\\.");
int len = domains.length;
if (len > 3) {
//
// www.xxx.com.cn
domainName = domains[len - 3] + "." + domains[len - 2] + "." + domains[len - 1];
} else if (len <= 3 && len > 1) {
// xxx.com or xxx.cn
domainName = domains[len - 2] + "." + domains[len - 1];
} else {
domainName = serverName;
}
}
if (domainName != null && domainName.indexOf(":") > 0) {
String[] ary = domainName.split("\\:");
domainName = ary[0];
}
System.out.println("domainName = " + domainName);
return domainName;
}
public static void deleteCookie(HttpServletRequest request, HttpServletResponse response, String cookieName) {
setCookie(request, response, cookieName, null, 0, false);
}
}