单点登陆之CAS

什么是单点登陆?

​ 单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

SSO单点登录访问流程主要有以下步骤:

1. 访问服务:SSO客户端发送请求访问应用系统提供的服务资源。
2. 定向认证:SSO客户端会重定向用户请求到SSO服务器。
3. 用户认证:用户身份认证。
4. 发放票据:SSO服务器会产生一个随机的Service Ticket。
5. 验证票据:SSO服务器验证票据Service Ticket的合法性,验证通过后,允许客户端访问服务。
6. 传输用户信息:SSO服务器验证票据通过后,传输用户认证结果信息给客户端。

CAS服务端部署

  1. 生成证书

    castest是证书名称,F:/keys/castest为存 放位置

keytool -genkey -alias castest -keyalg RSA -keystore F:/keys/castest

在这里插入图片描述

  1. 导出证书

    keytool -export -file F:/keys/castest.crt -alias castest -keystore F:/keys/castest
    

在这里插入图片描述

  1. 将证书导入到客户端JRE中(注意、是导入JRE中),如果security中已经存在cacerts,需要先将其删除。
keytool -import -keystore "D:\java\jdk\jre\lib\security\cacerts" -file F:/keys/castest.crt -alias castest

在这里插入图片描述

  1. 配置服务器端

    1、从https://github.com/apereo/cas/releases上下载cas服务器端cas-server-4.0.0-release.zip,在modules目录下找到cas-server-webapp-4.0.0.war,将其复制到%TOMCAT_HOME%\webapps下,并将名称改为cas.war

    2、修改%TOMCAT_HOME%\conf\server.xml文件

    <Connector protocol="org.apache.coyote.http11.Http11Protocol" port="8443" minSpareThreads="5" maxSpareThreads="75" enableLookups="true" disableUploadTimeout="true" acceptCount="100" maxThreads="200" scheme="https"  secure="true" SSLEnabled="true" keystoreFile="F:/keys/castest"<!—生成证书时的路径,证书名--> keystorePass="castest"<!—证书密码--> truststoreFile="D:/java/jdk/jre/lib/security/cacerts"<!-jre证书目录-> 
    clientAuth="false" sslProtocol="TLS"/>
    

    3、在C:\Windows\System32\drivers\etc\hosts里增加ip地址与域名之间的联系

    127.0.0.1       sso.castest.com
    

    4、测试、启动tomcat:https://localhost:8443/

    https://localhost:8443/cas/login:cas登陆页面,没有配置数据库之前,账号密码相同即可登陆

CAS客户端应用

cas配置文件

package intellif.szwj.cas;

import org.jasig.cas.client.authentication.AuthenticationFilter;
import org.jasig.cas.client.util.HttpServletRequestWrapperFilter;
import org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.embedded.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;

@Configuration
@ConditionalOnProperty(name = "cas.login", havingValue = "true")
public class CasFilterConfig {

    @Value("${cas.server.client.url}")
    private String appUrl;

    @Value("${cas.server.url}")
    private String casUrl;

    @Value("${cas.server.login.url}")
    private String casServerLoginUrl;

    /**
     * 单点登录认证
     * @return
     */
    @Bean
    public FilterRegistrationBean authenticationFilter(){
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(new AuthenticationFilter());
        registrationBean.addUrlPatterns("/wjcas/login");
        registrationBean.setName("CAS Filter");
        registrationBean.addInitParameter("casServerLoginUrl",casServerLoginUrl);
        registrationBean.addInitParameter("serverName", appUrl );
        registrationBean.setOrder(3);
        return registrationBean;
    }

    /**
     * 单点登录校验
     * @return
     */
    @Bean
    public FilterRegistrationBean cas20ProxyReceivingTicketValidationFilter(){
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(new Cas20ProxyReceivingTicketValidationFilter());
        registrationBean.addUrlPatterns("/wjcas/login");
        registrationBean.setName("CAS Validation Filter");
        registrationBean.addInitParameter("casServerUrlPrefix", casUrl );
        registrationBean.addInitParameter("serverName", appUrl );
        registrationBean.setOrder(4);
        return registrationBean;
    }

    /**
     * 单点登录请求包装
     * @return
     */
    @Bean
    public FilterRegistrationBean httpServletRequestWrapperFilter(){
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(new HttpServletRequestWrapperFilter());
        registrationBean.addUrlPatterns("/wjcas/login");
        registrationBean.setName("CAS HttpServletRequest Wrapper Filter");
        registrationBean.setOrder(5);
        return registrationBean;
    }

    /**
     * 单点登录本地用户信息
     * @return
     */
    @Bean
    public FilterRegistrationBean localRedirectFilterBean(){
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(localRedirectFilter());
        registrationBean.addUrlPatterns("/wjcas/login");
        registrationBean.setName("localRedirectFilter");
        registrationBean.setOrder(6);
        return registrationBean;
    }
    /**
     * 单点登录本地用户信息
     * @return
     */
    @Bean
    public FilterRegistrationBean localLoginFilterBean(){
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(localLoginFilter());
        registrationBean.addUrlPatterns("/locallogin/login");
        registrationBean.setName("localLoginFilter");
        registrationBean.setOrder(7);
        return registrationBean;
    }

    @Bean
    public Filter localRedirectFilter() {
        return new LocalRedirectFilter();
    }

    @Bean
    public Filter localLoginFilter() {
        return new LocalLoginFilter();
    }
}

前端点击CAS登陆发送http://127.0.0.1:8083/api/wjcas/login请求

在这里插入图片描述

cas进行拦截跳转至cas.server.login.url

输入用户名、密码点击登陆进入重定向拦截器

package intellif.szwj.cas;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jasig.cas.client.util.AbstractCasFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class LocalRedirectFilter implements Filter {
    private static Logger logger =  LogManager.getLogger(LocalRedirectFilter.class);

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Value("${cas.client.redirect.url}")
    private String redirectUrl;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        logger.info("==========================应用系统拦截器开始============================");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
            ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        // _const_cas_assertion_是CAS中存放登录用户名的session标志
        Object object = httpRequest.getSession().getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION);
        if (object != null) {
            String loginCode = httpRequest.getRemoteUser();
            httpResponse.sendRedirect(redirectUrl + "?loginCode="+loginCode);
            return;
        }
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        logger.info("==========================应用系统拦截器结束============================");
    }
}

进入重定向地址http://127.0.0.1:8083/api/caslogin.html?loginCode=superuser

重新加载cas登陆页面,发送http://127.0.0.1:8083/api/locallogin/login请求

LocalLoginFilter拦截器进行拦截,先获取用户名,然后查询数据库,最后模拟登陆获取token值

package intellif.szwj.cas;

import intellif.utils.HttpUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.entity.StringEntity;
import org.apache.http.message.BasicHeader;
import org.apache.http.protocol.HTTP;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class LocalLoginFilter implements Filter {
    private static Logger logger =  LogManager.getLogger(LocalLoginFilter.class);

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Value("${cas.client.token.url}")
    private String tokenUrl;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        logger.info("==========================应用系统拦截器开始============================");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
            ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        String pathCode = request.getParameter("loginCode");
        if (StringUtils.isNotEmpty(pathCode)) {
            String result = "";
            StringBuilder sb = new StringBuilder();
            sb.append("select login,password from intellif_base.t_user where login_code = '").append(pathCode).append("'");
            List<Map<String, Object>> userList = jdbcTemplate.queryForList(sb.toString());
            if (userList.size() > 0) {
                String login = userList.get(0).get("login").toString();
                String password = userList.get(0).get("password").toString();
                Map<String, String> headMap = new HashMap<>(16);
                StringBuilder tokenRequest = new StringBuilder("password=").append(password);
                tokenRequest.append("&username=").append(login);
                tokenRequest.append("&grant_type=password&scope=read+write&client_secret=123456&client_id=clientapp");
                logger.info(tokenRequest.toString());
                headMap.put("Authorization", "Basic Y2xpZW50YXBwOjEyMzQ1Ng==");
                headMap.put("Content-Type", "application/x-www-form-urlencoded");
                StringEntity stringEntity = new StringEntity(tokenRequest.toString(), "UTF-8");
                stringEntity.setContentType("application/json");
                stringEntity.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE, "application/json"));
                result = HttpUtils.doPost(tokenUrl, headMap, null, stringEntity);
                if (request == null) {
                    result = "获取系统权限失败,请联系管理员";
                }
            } else {
                result = "无此用户,请联系管理员";
            }
            response.setContentType("application/json; charset=utf-8");
            response.setCharacterEncoding("UTF-8");
            OutputStream out = response.getOutputStream();
            out.write(result.getBytes("UTF-8"));
            out.flush();
            return;
        }

        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        logger.info("==========================应用系统拦截器结束============================");
    }
}

前端登陆页面

步骤:

1、CAS登陆按钮点击发送http://127.0.0.1:8083/api/wjcas/login请求;

2、返回带有loginCode的数据http://127.0.0.1:8083/api/caslogin.html?loginCode=superuser;

3、重新加载页面时如果loginCode不为空,则会发送http://127.0.0.1:8083/api/caslogin.html?loginCode=superuser请求,返回token;

4、将token放入前端session里,加载http://localhost:5000/;

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>login</title>
<style type="text/css">
*{
	margin: 0;
	padding: 0;
}
#wrap {
	height: 719px;
	width: 100;
	background-repeat: no-repeat;
	background-position: center center;
	position: relative;
}
#head {
	height: 120px;
	width: 100;
	background-color: #66CCCC;
	text-align: center;
	position: relative;
}

#wrap .logGet {
	height: 408px;
	width: 368px;  
	position: absolute;
	background-color: #FFFFFF;
	top: 10%;
	right: 35%;
}
.logC button {
	width: 100%;
	height: 45px;
	background-color: #40a9ff;
	border: none;
	color: white;
	font-size: 18px;
}



#wrap .logGet .logC {
	width: 86%;
	margin-top: 0px;
	margin-right: auto;
	margin-bottom: 0px;
	margin-left: auto;
}
 
 
.title {
	font-family: "宋体";
	color: #FFFFFF;
	position: absolute;
	top: 50%;
	left: 50%;
	transform: translate(-50%, -50%);  /* 使用css3的transform来实现 */
	font-size: 36px;
	height: 40px;
	width: 50%;
}
 

</style>
</head>
 
<body>
<div class="header" id="head">
  <div class="title">吴江公安分局动态人像识别系统</div>
</div>
	
<div class="wrap" id="wrap">
	<div class="logGet">
			<div class="logC">
				<button onclick="doLoginPre()">CAS 登录</button></a>
			</div>
		</div>
</div>
<script>
var serverIp = "127.0.0.1";
var webUrl = "http://" + serverIp + ":8083/api/static/index.html";
var apiUrl = "http://" + serverIp + ":8083/api";
var casLogin = apiUrl + "/wjcas/login";
var casLogin2 = apiUrl + "/locallogin/login";
var getRightUrl = apiUrl + "/intellif/user/right/";
var languageListUrl = apiUrl + "/intellif/national/language/list";

window.onload=function(){
	
	//先清除登录缓存
	window.localStorage.clear();

	var post_data = getRequest();
	var loginCode = post_data['loginCode'];
	if (!loginCode) {
		return;
	}
	// Check browser support
	if (typeof(Storage) !== "undefined") {
		try {
			doAjax(casLogin2+"?loginCode="+loginCode,"GET",null,function(res) {
				if (isError(res)) {
					alert("CAS login error!" + res);
					return;
				}
				var resObj = JSON.parse(res);
				var token = resObj.access_token;
				document.cookie="token="+token;
				var userInfo = resObj;
				var userInfoName = userInfo.oauth_AIK_user_info.login;
				doAjax(getRightUrl+userInfoName,"GET",null,function(res1) {
					if (isError(res1)) {
						alert("PKI login error!" + res1);
						window.location=webUrl;
						return;
					}
					var res1Obj = JSON.parse(res1);
					userInfo.oauth_AIK_user_info = res1Obj.data.userinfo;
					userInfo.oauth_AIKK_role_info_s = res1Obj.data.roleInfoList;
					userInfo.resIds = res1Obj.data.roleInfoList[0].resIds;
					//将获取到的用户信息存储在本地的localstorage中!
					window.localStorage.setItem(
					  'userInfo',
					  JSON.stringify(userInfo)
					);
					//兼容深目1.x存储的oauth
					window.localStorage.setItem('oauth', JSON.stringify(userInfo));
					window.localStorage.setItem(
					  'currentSessionTime',
					  new Date().getTime()
					);
					if (res1Obj.data.userinfo.locale) {
					  window.localStorage.setItem(
						'userLocale',
						JSON.stringify(res1Obj.data.userinfo.locale)
					  );
					}
					doAjax(languageListUrl,"GET",null,function(res2) {
						if (isError(res2)) {
							alert("PKI login error!" + res2);
							window.location=webUrl;
							return;
						}
						var res2Obj = JSON.parse(res2);
						// 本地存储可支持的语言列表
						window.localStorage.setItem(
						  'languageList',
						  JSON.stringify({ languageList: res2Obj.data })
						);
						window.location=webUrl;
					}, token);
				}, token);
				
			});
		} catch (e) {
			alert("CAS登录失败:" + e);
		}
	} else {
		document.getElementById("result").innerHTML = "sorry!your Browsers do not support Web Storage ...";
	}
}
function doLoginPre() {
	window.location=casLogin;
}
 
//获得URL后面带的参数,并封装成对象
function getRequest() {
	var request = new Object();
	//获得URL问号后面的字符串,包括问号
	var param = window.location.search;
	if (param != '') {
		//去掉问号
		param = param.substring(1);
		//用‘&’,分离出带有的参数
		var params = param.split("&");
		//用‘=’,循环将参数分离成key-value形式,并封装到对象中
		for (var i = 0; i < params.length; i++) {
			var kv = params[i].split("=");
			request[kv[0]] = kv[1];
		}
	}
	return request;
}

function doAjax(url, method, data, callback, token) {
	var xmlhttp;
	if (window.XMLHttpRequest)
	{
		// IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
		xmlhttp=new XMLHttpRequest();
	}
	else
	{
		// IE6, IE5 浏览器执行代码
		xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
	}
	xmlhttp.open(method,url,true);
	xmlhttp.setRequestHeader("Content-type","application/json; charset=utf-8");
	if (token) {
		xmlhttp.setRequestHeader("Authorization","Bearer " + token);
	}
	if (method == "POST") {
		xmlhttp.send(JSON.stringify(data));
	} else {
		xmlhttp.send();
	}
	
	xmlhttp.onreadystatechange=function()
	{
		if (xmlhttp.readyState==4)
		{	if (xmlhttp.status==200) {
				if (callback)	 {
					callback(xmlhttp.responseText);
				}
			} else {
				callback("error");
			}
		} 
	}
}

function isError(jsonStr) {
	try {
		var obj = JSON.parse(jsonStr);
		if (obj['errCode'] && obj['errCode'] != '0') {
			return true;
		} else {
			return false;
		}
	} catch (e) {
		return true;
	}
}
</script>
	
</body>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值