统一身份认证平台单点登录CAS对接规范
============================

本文档描述了配置统一身份认证系统的对接的规范，以及配置的详细说明。单点登录采用CAS框架，具体配置请参考本规范。

# 系统对接流程
![输入图片说明](https://img.bzhz.jnbygroup.com/cas%E8%AE%A4%E8%AF%81%E6%97%B6%E5%BA%8F%E5%9B%BE.png "在这里输入图片标题")

# 项目配置

注 : 下文内{server}为环境域名,sso地址： https://sso.jnby.com:8443/cas

# 配置maven环境依赖
```xml
<dependency>
    <groupId>org.jasig.cas.client</groupId>
    <artifactId>cas-client-core</artifactId>
    <version>3.5.0</version>
</dependency>
```

# 基于Vue的前后端分离架构

对接流程:
![输入图片说明](https://img.bzhz.jnbygroup.com/cas%E5%8D%95%E7%82%B9%E7%99%BB%E5%BD%95.png "在这里输入图片标题")

1.前端代码cookie设置如下:
```javascript
    Vue.ls.set('Access-Token', result.token, 7 * 24 * 60 * 60 * 1000)
    Vue.ls.set('Login_Username', userInfo.username, 7 * 24 * 60 * 60 * 1000)
    Vue.ls.set('Login_Userinfo', userInfo, 7 * 24 * 60 * 60 * 1000)
```

2.前端接口请求token传递说明：
- 前后端分离架构，前端通过cookie中Access-Token的有效性，判断是否需要进行sso登录认证
- 前端token携带需要通过head头部传递，头部别名为X-Access-Token
- cookie设置共享，则domain为*.jnby.com

3.服务端token加解密规则
```java
//token加解密	
  public static final long EXPIRE_TIME = 30 * 60 * 1000;

	/**
	 * 校验token是否正确
	 *
	 * @param token  密钥
	 * @param secret 用户的密码
	 * @return 是否正确
	 */
	public static boolean verify(String token, String username, String secret) {
		try {
			// 根据密码生成JWT效验器
			Algorithm algorithm = Algorithm.HMAC256(secret);
			JWTVerifier verifier = JWT.require(algorithm).withClaim("username", username).build();
			// 效验TOKEN
			DecodedJWT jwt = verifier.verify(token);
			return true;
		} catch (Exception exception) {
			return false;
		}
	}

	/**
	 * 获得token中的信息无需secret解密也能获得
	 *
	 * @return token中包含的用户名
	 */
	public static String getUsername(String token) {
		try {
			DecodedJWT jwt = JWT.decode(token);
			return jwt.getClaim("username").asString();
		} catch (JWTDecodeException e) {
			return null;
		}
	}

	/**
	 * 生成签名,5min后过期
	 *
	 * @param username 用户名
	 * @param secret   用户的密码
	 * @return 加密的token
	 */
	public static String sign(String username, String secret) {
		Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
		Algorithm algorithm = Algorithm.HMAC256(secret);
		// 附带username信息
		return JWT.create().withClaim("username", username).withExpiresAt(date).sign(algorithm);

	}

	/**
	 * 根据request中的token获取用户账号
	 *
	 * @param request
	 * @return
	 * @throws JeecgBootException
	 */
	public static String getUserNameByToken(HttpServletRequest request) throws Exception {
		String accessToken = request.getHeader("X-Access-Token");
		String username = getUsername(accessToken);
		if (oConvertUtils.isEmpty(username)) {
			throw new Exception("未获取到用户");
		}
		return username;
	}
```
4.CAS单点登录客户端回调后的ticket登录认证
- 前端应用跳转认证系统(https://sso.jnby.com:8443/cas/login?service=http://abc.com)，认证系统认证完成重定向到前端应用(http://abc.com?ticket=xxxxx)，并且携带ticket
- 前端获取ticket，并请求自身的服务应用进行ticket校验
- 应用端校验成功，并获取认证后返回的user信息
- 应用服务端通过user生成自己的局部token返回给前端
- 前端获取token生成对应的cookie值

4.1服务端validateLogin认证部分代码
```java
	@GetMapping("/validateLogin")
	public Object validateLogin(@RequestParam(name="ticket") String ticket,
								@RequestParam(name="service") String service,
								HttpServletRequest request,
								HttpServletResponse response) throws Exception {
		Result<JSONObject> result = new Result<JSONObject>();
		log.info("Rest api login.");
		try {
			String validateUrl = prefixUrl+"/p3/serviceValidate";
			String res = getSTValidate(validateUrl, ticket, service);
			final String principal = Utils.getTextForElement(res, "user");
			if (StringUtils.isEmpty(principal)) {
	            throw new Exception("No principal was found in the response from the CAS server.");
	        }
		    //1. 校验用户是否有效
            UserPrincipal user = UserPrincipal.toJSON(principal)
	  		SysUser sysUser = sysUserService.getUserByName(user.getUserName);
	  		result = sysUserService.checkUserIsEffective(sysUser);
	  		if(!result.isSuccess()) {
	  			return result;
	  		}
	  		
	  		//此处代码参考 3.服务端token加解密规则
	 		String token = JwtUtil.sign(sysUser.getUsername(), sysUser.getPassword());
	 		// 设置超时时间
	 		redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, token);
	 		redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME*2 / 1000);
			obj.put("token", token);
			obj.put("userInfo", sysUser);
			result.setResult(obj);
			result.success("登录成功");

		} catch (Exception e) {
			//e.printStackTrace();
			result.error500(e.getMessage());
		}
		return new HttpEntity<>(result);
	}
```
4.2CAS认证基于REST方式认证TICKET
```java
	/**
     * 验证ST
     */
    public static String getSTValidate(String url,String st, String service){
		try {
			url = url+"?service="+service+"&ticket="+st+"&format=json";
			CloseableHttpClient httpclient = createHttpClientWithNoSsl();
			HttpGet httpget = new HttpGet(url);
			HttpResponse response = httpclient.execute(httpget);
	        String res = readResponse(response);
	        return res == null ? null : (res == "" ? null : res);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return "";
	}
```
4.3CAS认证返回的JSON响应体:
```json
{
    "serviceResponse":{
        "authenticationSuccess":{
            "user":"{\"birthday\":\"1990-10-25\",\"sex\":\"0\",\"mobile\":\"18668166209\",\"id\":\"22366\",\"email\":\"yuanxiaozhong22366@jnby.com\",\"username\":\"yuanxiaozhong22366\",\"lastname\":\"袁小忠\"}",
            "attributes":{
                "credentialType":[
                    "UsernamePasswordCredential"
                ],
                "isFromNewLogin":[
                    true
                ],
                "authenticationDate":[
                    1626675944.078
                ],
                "authenticationMethod":[
                    "com.jnby.config.CustomUsernamePasswordAuthentication"
                ],
                "successfulAuthenticationHandlers":[
                    "com.jnby.config.CustomUsernamePasswordAuthentication"
                ],
                "longTermAuthenticationRequestTokenUsed":[
                    false
                ]
            }
        }
    }
}
```


