单点登录SSO----JSON Web Token(JWT)机制

JSON Web Token(JWT)机制

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.sxt</groupId>
	<artifactId>sso-jwt</artifactId>
	<version>1.0</version>
	<packaging>war</packaging>
	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>5.0.6.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>5.0.6.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aspects</artifactId>
			<version>5.0.6.RELEASE</version>
		</dependency>
		<!-- JWT核心依赖 -->
		<dependency>
			<groupId>com.auth0</groupId>
			<artifactId>java-jwt</artifactId>
			<version>3.3.0</version>
		</dependency>
		<!-- java开发JWT的依赖jar包。 -->
		<dependency>
		    <groupId>io.jsonwebtoken</groupId>
		    <artifactId>jjwt</artifactId>
		    <version>0.9.0</version>
		</dependency>
		<!-- 给springmvc提供的响应扩展。@ResponseBody -->
		<dependency>
		    <groupId>com.fasterxml.jackson.core</groupId>
		    <artifactId>jackson-databind</artifactId>
		    <version>2.9.5</version>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.2</version>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>servlet-api</artifactId>
			<version>2.5</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>javax.servlet.jsp</groupId>
			<artifactId>jsp-api</artifactId>
			<version>2.2</version>
			<scope>provided</scope>
		</dependency>
	</dependencies>
	<build>
		<pluginManagement>
			<plugins>
				<!-- 配置Tomcat插件 -->
				<plugin>
					<groupId>org.apache.tomcat.maven</groupId>
					<artifactId>tomcat7-maven-plugin</artifactId>
					<version>2.2</version>
				</plugin>
			</plugins>
		</pluginManagement>
		<plugins>
			<plugin>
				<groupId>org.apache.tomcat.maven</groupId>
				<artifactId>tomcat7-maven-plugin</artifactId>
				<configuration>
					<port>80</port>
					<path>/</path>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

JWTSubject.java

package com.sxt.sso.commons;

/**
 * 作为Subject数据使用。也就是payload中保存的public claims
 * 其中不包含任何敏感数据
 * 开发中建议使用实体类型。或BO,DTO数据对象。
 */
public class JWTSubject {

	private String username;

	public JWTSubject() {
		super();
	}

	public JWTSubject(String username) {
		super();
		this.username = username;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}
	
}

JWTUtils.java

package com.sxt.sso.commons;

import java.util.Date;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;

/**
 * JWT工具
 */
public class JWTUtils {
	
	// 服务器的key。用于做加解密的key数据。 如果可以使用客户端生成的key。当前定义的常亮可以不使用。
	private static final String JWT_SECERT = "test_jwt_secert" ;
	private static final ObjectMapper MAPPER = new ObjectMapper();
	public static final int JWT_ERRCODE_EXPIRE = 1005;//Token过期
	public static final int JWT_ERRCODE_FAIL = 1006;//验证不通过

	public static SecretKey generalKey() {
		try {
			// byte[] encodedKey = Base64.decode(JWT_SECERT); 
			// 不管哪种方式最终得到一个byte[]类型的key就行
			byte[] encodedKey = JWT_SECERT.getBytes("UTF-8");
		    SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
		    return key;
		} catch (Exception e) {
			e.printStackTrace();
			 return null;
		}
	}
	/**
	 * 签发JWT,创建token的方法。
	 * @param id  jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
	 * @param iss jwt签发者
	 * @param subject jwt所面向的用户。payload中记录的public claims。当前环境中就是用户的登录名。
	 * @param ttlMillis 有效期,单位毫秒
	 * @return token, token是一次性的。是为一个用户的有效登录周期准备的一个token。用户退出或超时,token失效。
	 * @throws Exception
	 */
	public static String createJWT(String id,String iss, String subject, long ttlMillis) {
		// 加密算法
		SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
		// 当前时间。
		long nowMillis = System.currentTimeMillis();
		// 当前时间的日期对象。
		Date now = new Date(nowMillis);
		SecretKey secretKey = generalKey();
		// 创建JWT的构建器。 就是使用指定的信息和加密算法,生成Token的工具。
		JwtBuilder builder = Jwts.builder()
				.setId(id)  // 设置身份标志。就是一个客户端的唯一标记。 如:可以使用用户的主键,客户端的IP,服务器生成的随机数据。
				.setIssuer(iss)
				.setSubject(subject)
				.setIssuedAt(now) // token生成的时间。
				.signWith(signatureAlgorithm, secretKey); // 设定密匙和算法
		if (ttlMillis >= 0) { 
			long expMillis = nowMillis + ttlMillis;
			Date expDate = new Date(expMillis); // token的失效时间。
			builder.setExpiration(expDate);
		}
		return builder.compact(); // 生成token
	}
	
	/**
	 * 验证JWT
	 * @param jwtStr
	 * @return
	 */
	public static JWTResult validateJWT(String jwtStr) {
		JWTResult checkResult = new JWTResult();
		Claims claims = null;
		try {
			claims = parseJWT(jwtStr);
			checkResult.setSuccess(true);
			checkResult.setClaims(claims);
		} catch (ExpiredJwtException e) { // token超时
			checkResult.setErrCode(JWT_ERRCODE_EXPIRE);
			checkResult.setSuccess(false);
		} catch (SignatureException e) { // 校验失败
			checkResult.setErrCode(JWT_ERRCODE_FAIL);
			checkResult.setSuccess(false);
		} catch (Exception e) {
			checkResult.setErrCode(JWT_ERRCODE_FAIL);
			checkResult.setSuccess(false);
		}
		return checkResult;
	}
	
	/**
	 * 
	 * 解析JWT字符串
	 * @param jwt 就是服务器为客户端生成的签名数据,就是token。
	 * @return
	 * @throws Exception
	 */
	public static Claims parseJWT(String jwt) throws Exception {
		SecretKey secretKey = generalKey();
		return Jwts.parser()
			.setSigningKey(secretKey)
			.parseClaimsJws(jwt)
			.getBody(); // getBody获取的就是token中记录的payload数据。就是payload中保存的所有的claims。
	}
	
	/**
	 * 生成subject信息
	 * @param subObj - 要转换的对象。
	 * @return java对象->JSON字符串出错时返回null
	 */
	public static String generalSubject(Object subObj){
		try {
			return MAPPER.writeValueAsString(subObj);
		} catch (JsonProcessingException e) {
			e.printStackTrace();
			return null;
		}
	}
	
}

JWTResponseData.java

package com.sxt.sso.commons;

/**
 * 发送给客户端的数据对象。
 * 商业开发中,一般除特殊请求外,大多数的响应数据都是一个统一类型的数据。
 * 统一数据有统一的处理方式。便于开发和维护。
 */
public class JWTResponseData {

	private Integer code;// 返回码,类似HTTP响应码。如:200成功,500服务器错误,404资源不存在等。
	
	private Object data;// 业务数据
	
	private String msg;// 返回描述
	
	private String token;// 身份标识, JWT生成的令牌。

	public Integer getCode() {
		return code;
	}

	public void setCode(Integer code) {
		this.code = code;
	}

	public Object getData() {
		return data;
	}

	public void setData(Object data) {
		this.data = data;
	}

	public String getMsg() {
		return msg;
	}

	public void setMsg(String msg) {
		this.msg = msg;
	}

	public String getToken() {
		return token;
	}

	public void setToken(String token) {
		this.token = token;
	}
	
}

JWTResult.java

package com.sxt.sso.commons;

import io.jsonwebtoken.Claims;

/**
 * 结果对象。
 */
public class JWTResult {

	/**
	 * 错误编码。在JWTUtils中定义的常量。
	 * 200为正确
	 */
	private int errCode;

	/**
	 * 是否成功,代表结果的状态。
	 */
	private boolean success;

	/**
	 * 验证过程中payload中的数据。
	 */
	private Claims claims;

	public int getErrCode() {
		return errCode;
	}

	public void setErrCode(int errCode) {
		this.errCode = errCode;
	}

	public boolean isSuccess() {
		return success;
	}

	public void setSuccess(boolean success) {
		this.success = success;
	}

	public Claims getClaims() {
		return claims;
	}

	public void setClaims(Claims claims) {
		this.claims = claims;
	}
	
}

JWTController.java

package com.sxt.sso.controller;

import java.util.UUID;

import javax.servlet.http.HttpServletRequest;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.sxt.sso.commons.JWTResponseData;
import com.sxt.sso.commons.JWTResult;
import com.sxt.sso.commons.JWTSubject;
import com.sxt.sso.commons.JWTUsers;
import com.sxt.sso.commons.JWTUtils;

@Controller
public class JWTController {

	@RequestMapping("/testAll")
	@ResponseBody
	public Object testAll(HttpServletRequest request){
		
		String token = request.getHeader("Authorization");
		JWTResult result = JWTUtils.validateJWT(token);
		
		JWTResponseData responseData = new JWTResponseData();
		
		if(result.isSuccess()){
			responseData.setCode(200);
			responseData.setData(result.getClaims().getSubject());
			// 重新生成token,就是为了重置token的有效期。
			String newToken = JWTUtils.createJWT(result.getClaims().getId(), 
					result.getClaims().getIssuer(), result.getClaims().getSubject(), 
					1*60*1000);
			responseData.setToken(newToken);
			return responseData;
		}else{
			responseData.setCode(500);
			responseData.setMsg("用户未登录");
			return responseData;
		}
		
	}
	
	@RequestMapping("/login")
	@ResponseBody
	public Object login(String username, String password){
		JWTResponseData responseData = null;
		// 认证用户信息。本案例中访问静态数据。
		if(JWTUsers.isLogin(username, password)){
			JWTSubject subject = new JWTSubject(username);
			String jwtToken = JWTUtils.createJWT(UUID.randomUUID().toString(), "sxt-test-jwt", 
					JWTUtils.generalSubject(subject), 1*60*1000);
			responseData = new JWTResponseData();
			responseData.setCode(200);
			responseData.setData(null);
			responseData.setMsg("登录成功");
			responseData.setToken(jwtToken);
		}else{
			responseData = new JWTResponseData();
			responseData.setCode(500);
			responseData.setData(null);
			responseData.setMsg("登录失败");
			responseData.setToken(null);
		}
		
		return responseData;
	}
	
}

index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="js/jquery.min.js"></script>
<script type="text/javascript">

function login(){
	var username = $("#username").val();
	var password = $("#password").val();
	var params = "username="+username+"&password="+password;
	$.ajax({
		'url' : '${pageContext.request.contextPath }/login',
		'data' : params,
		'success' : function(data){
			if(data.code == 200){
				var token = data.token;
				// web storage的查看 - 在浏览器的开发者面板中的application中查看。
				// local storage - 本地存储的数据。 长期有效的。
				// session storage - 会话存储的数据。 一次会话有效。
				var localStorage = window.localStorage; // 浏览器提供的存储空间。 根据key-value存储数据。
				localStorage.token = token;
			}else{
				alert(data.msg);
			}
		}
	});
}

function setHeader(xhr){ // XmlHttpRequest
	xhr.setRequestHeader("Authorization",window.localStorage.token);
}

function testLocalStorage(){
	$.ajax({
		'url' : '${pageContext.request.contextPath}/testAll',
		'success' : function(data){
			if(data.code == 200){
				window.localStorage.token = data.token;
				alert(data.data);
			}else{
				alert(data.msg);
			}
		},
		'beforeSend' : setHeader
	});
}

</script>
</head>
<body >
	<center>
		<table>
			<caption>登录测试</caption>
			<tr>
				<td style="text-align: right; padding-right: 5px">
				登录名:
				</td>
				<td style="text-align: left; padding-left: 5px">
				<input type="text" name="username" id="username"/>
				</td>
			</tr>
			<tr>
				<td style="text-align: right; padding-right: 5px">
				密码:
				</td>
				<td style="text-align: left; padding-left: 5px">
				<input type="text" name="password" id="password"/>
				</td>
			</tr>
			<tr>
				<td style="text-align: right; padding-right: 5px" colspan="2">
				<input type="button" value="登录" onclick="login();" />
				</td>
			</tr>
		</table>
	</center>
	<input type="button" value="testLocalStorage" onclick="testLocalStorage();"/>
</body>
</html>

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值