项目使用cas的restful协议进行单点登录

一、为什么要使用restful协议

可以使用原本系统的登录界面,不用去改cas默认的界面。改实际的项目远比demo测试时复杂的多,我这里项目用的是springmvc

二、实现思路:

(1)调整cas服务端的配置文件可以参考springboot+shiro+cas5.2通过RESTful协议进行sso单点登录

(2)在原本的登录逻辑上添加登录成功后,通过用户名密码获取tgt的代码,并把tgt添加到session里

//利用restful协议登录cas服务端
String tgt = new CasServerUtil().getTGT(loginname, password);
if (StringUtils.isNotBlank(tgt)) {
    request.getSession().setAttribute("tgt", tgt);
    return "true";
}

(3)在js调用ajax返回成功的方法里,先把tgt存到cookie中,再调用获取ST(ticket)的方法,拿到ST后通过重定向登录成功

	/**
	 *
	 * @Date 2020年8月10日 下午4:57:05
	 * @Description 重定向
	 * @Fcunction redirect
	 * @return String
	 *
	 */
	@RequestMapping(value="redirect",method =RequestMethod.GET)
	public String redirect(HttpServletRequest request, HttpServletResponse response,String tgt){
		if (StringUtils.isBlank(tgt)) {
			tgt = (String) request.getSession().getAttribute("tgt");
			Cookie cookie = new Cookie("tgt", tgt);
			// 2.配置Cookie对象
			cookie.setComment("cas");    // Cookie描述
			cookie.setMaxAge(24 * 60 * 60);            // Cookie有效时间
			cookie.setPath("/");                 // Cookie有效路径
			// 3.通过response对象将Cookie写入浏览器,当然需要解决中文乱码问题,否则会抛出异常
			// java.lang.IllegalArgumentException: Control character in cookie value, consider BASE64 encoding your value
			response.setCharacterEncoding("UTF-8");
			response.setContentType("text/html;charset=UTF-8");
			response.addCookie(cookie);
		}else {
			request.getSession().setAttribute("tgt", tgt);
		}
		String st = new CasServerUtil().getST(tgt);
		return "redirect:" + CommonConfigUtils.serverA + "shiro-cas?ticket=" + st;
	}

(4)现在单个系统已经能通过原本的登录页成功登录,并让cas服务端成功认证,且cookie中也有了tgt。接下来是修改拦截器,我这里项目原本的逻辑是,直接访问业务模块,会进拦截器,通过判断shiro有无登录用户,未登录则返回登录页面,登录了返回业务页面。这里给拦截器中未登录的的地方增加读取cookie中的tgt,把原有访问业务的地址存session中(避免后面登录成功返回首页),以及通过重定向登录

if (user != null) {//判断有用户登录时,进一步判断有没有相应的权限。
	fc.doFilter(req, resp);
	return;
} else {//没有登录用户时处理
	ShiroHttpServletRequest httpservletrequest = (ShiroHttpServletRequest) request;
	//获取cookie中的tgt
	Cookie[] cookies = httpservletrequest.getCookies();
	for (Cookie cookie : cookies) {
		String name = cookie.getName();
		if ("tgt".equals(name)) {
			String tgt = cookie.getValue();
			if (StringUtils.isNotBlank(tgt)) {
				//把原本访问业务的地址存到session中
				StringBuffer requestURL = httpservletrequest.getRequestURL();
				int con = requestURL.indexOf("con");
				if (con > -1) {
					String url = requestURL.substring(con);
					httpservletrequest.getSession().setAttribute("con", url);
				}
				//通过重定向登录
				String st = new CasServerUtil().getST(tgt);
				resp.sendRedirect(CommonConfigUtils.serverA + "shiro-cas?ticket=" + st);
				return;
			}
		}
	}
	resp.sendRedirect(req.getContextPath() +"/login");
}

(5)重写CasFilter,主要是重写登录成功的方法onLoginSuccess(),改完后的效果是普通登录返回首页,通过单点登录返回业务页面,每次获取完session中的业务url后删除该条session

/**
 * 重新登录成功返回路径方法
 * @param token
 * @param subject
 * @param request
 * @param response
 * @return
 * @throws Exception
 */
@Override
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
	String successUrl = null;
	boolean contextRelative = true;
	SavedRequest savedRequest = getAndClearSavedRequest(request);
	if (savedRequest != null && savedRequest.getMethod().equalsIgnoreCase("GET")) {
		successUrl = savedRequest.getRequestUrl();
		contextRelative = false;
	}

	if (successUrl == null) {
		ShiroHttpServletRequest httpservletrequest = (ShiroHttpServletRequest) request;
		HttpSession session = httpservletrequest.getSession();
		Object con = session.getAttribute("con");
		if (con != null) {
			successUrl = con.toString();
			session.removeAttribute("con");
		}else {
			successUrl = "/main";
		}
	}

	if (successUrl == null) {
		throw new IllegalStateException("Success URL not available via saved request or via the successUrlFallback method parameter. One of these must be non-null for issueSuccessRedirect() to work.");
	} else {
		issueRedirect(request, response, successUrl, (Map)null, contextRelative);
	}
	return false;
}

完整重写CasFilter的类

package com.enter.net.frame.shiro;

import org.apache.commons.lang.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.cas.CasFilter;
import org.apache.shiro.cas.CasToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.util.SavedRequest;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Field;
import java.util.Map;

import static org.apache.shiro.web.util.WebUtils.getAndClearSavedRequest;
import static org.apache.shiro.web.util.WebUtils.issueRedirect;

public class CasLoginFilter extends CasFilter {
	private static final String TICKET_PARAMETER = "ticket";

	public CasLoginFilter() {
	}

	@Override
	public AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
		// 获取请求的ticket
		HttpServletRequest httpRequest = (HttpServletRequest) request;
		String ticket = getRequestTicket(httpRequest);
		if (StringUtils.isEmpty(ticket)) {
			return null;
		}
		return new CasToken(ticket);
	}

	/**
	 * 拒绝除了option以外的所有请求
	 **/
	@Override
	public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
		if (((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())) {
			return true;
		}
		return false;
	}

	@Override
	public boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
		// 获取ticket,如果不存在,直接返回false
		String ticket = getRequestTicket((HttpServletRequest) request);
		if (StringUtils.isEmpty(ticket)) {
			return false;
		}
		return this.executeLogin(request, response);
	}

	@Override
	protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
		//获取AuthenticationToken实体
		AuthenticationToken token = createToken(request, response);
		if (token == null) {
			String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +
					"must be created in order to execute a login attempt.";
			throw new IllegalStateException(msg);
		}
		try {
			Subject subject = getSubject(request, response);
			//执行分子系统的Shrio认证与授权
			subject.login(token);
			SavedRequest shiroSavedRequest = (SavedRequest) SecurityUtils.getSubject().getSession(false).getAttribute("shiroSavedRequest");
			if (shiroSavedRequest != null) {
				//修改url地址为登录首页,否则会跳转到之前手输的地址容易404,由于没有set方法,所以使用反射
				Class clazz  = shiroSavedRequest.getClass();
				Field requestURI = clazz.getDeclaredField("requestURI");
				requestURI.setAccessible(true);
				requestURI.set(shiroSavedRequest,this.getSuccessUrl());
			}
			return onLoginSuccess(token, subject, request, response);
		} catch (AuthenticationException e) {
			return onLoginFailure(token, e, request, response);
		}
	}

	/**
	 * 重新登录成功返回路径方法
	 * @param token
	 * @param subject
	 * @param request
	 * @param response
	 * @return
	 * @throws Exception
	 */
	@Override
	protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
		String successUrl = null;
		boolean contextRelative = true;
		SavedRequest savedRequest = getAndClearSavedRequest(request);
		if (savedRequest != null && savedRequest.getMethod().equalsIgnoreCase("GET")) {
			successUrl = savedRequest.getRequestUrl();
			contextRelative = false;
		}

		if (successUrl == null) {
			ShiroHttpServletRequest httpservletrequest = (ShiroHttpServletRequest) request;
			HttpSession session = httpservletrequest.getSession();
			Object con = session.getAttribute("con");
			if (con != null) {
				successUrl = con.toString();
				session.removeAttribute("con");
			}else {
				successUrl = "/main";
			}
		}

		if (successUrl == null) {
			throw new IllegalStateException("Success URL not available via saved request or via the successUrlFallback method parameter. One of these must be non-null for issueSuccessRedirect() to work.");
		} else {
			issueRedirect(request, response, successUrl, (Map)null, contextRelative);
		}
		return false;
	}

	/**
	 * 获取请求的ticket
	 */
	private String getRequestTicket(HttpServletRequest httpRequest) {
		// 从参数中获取ticket
		String ticket = httpRequest.getParameter(TICKET_PARAMETER);
		if (StringUtils.isEmpty(ticket)) {
			// 如果为空的话,则从header中获取参数
			ticket = httpRequest.getHeader(TICKET_PARAMETER);
		}
		return ticket;
	}
}

用到的工具类

package com.enter.net.util;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.DeleteMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.lang.StringUtils;

import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class CasServerUtil {

	//登录地址的token
	private String get_token_url = "http://192.168.10.77:8443/cas/v1/tickets/";

	//目标返回的服务器的url, 同访问的地址必须完全一致,不然就会报错。
	private String taget_url = "http://192.168.10.77:8010/shiro_cas";

	private String ticket = "";

	public String getTicket() {
		return ticket;
	}

	public void setTicket(String ticket) {
		this.ticket = ticket;
	}

	public CasServerUtil(){
		String serverUrl = CommonConfigUtils.serverUrl;
		String serverB = CommonConfigUtils.serverB + "shiro-cas";
		if (StringUtils.isNotBlank(serverUrl)) {
			get_token_url = serverUrl + "v1/tickets/";
			taget_url = serverB;
		}
	}

	public CasServerUtil(String tgt,String taget_url) {
		this.taget_url = taget_url;
		this.ticket = getST(tgt);
	}

	public static void main(String [] args) throws Exception {
		String username ="xtcs";
		String password ="Aa123456.";
		CasServerUtil casServerUtil = new CasServerUtil();
		String st = casServerUtil.getST(username, password);
		System.out.println(st);
	}

	/**
	 * 通过tgt,登出
	 * @param tgt
	 * @return String
	 */
	public void logout(String tgt){
		HttpClient client = new HttpClient();
		DeleteMethod method = new DeleteMethod(get_token_url + tgt + "/");
		try{
			client.executeMethod(method);
			String response = method.getResponseBodyAsString();
			int status = method.getStatusCode();
		}catch (IOException e){
			e.printStackTrace();
		}finally{
			method.releaseConnection();
		}
	}

	/**
	 * 通过用户名密码获取tgt,并根据tgt获取ticket
	 * @param username
	 * @param password
	 * @return String
	 */
	public String getST(String username,String password){
		String tgt = getTGT(username, password);
		if(StringUtils.isEmpty(tgt)){
			return "";
		}
		return getST(tgt);
	}

	/**
	 * 根据用户名、密码获取tgt
	 * @return String
	 */
	public String getTGT(String username,String password){
		String tgt = "";
		HttpClient client = new HttpClient();
		PostMethod method = new PostMethod(get_token_url);
		method.setRequestBody(new NameValuePair[]{new NameValuePair("username", username), new NameValuePair("password", password)});
		try{
			client.executeMethod(method);
			String response = method.getResponseBodyAsString();
			int status = method.getStatusCode();
			switch (status){
				case HttpStatus.SC_CREATED: // Created
					Matcher matcher = Pattern.compile(".*action=\".*/(.*?)\".*").matcher(response);
					if (matcher.matches()){
						tgt = matcher.group(1);
					}
					break;
				default:
					break;
			}
		}catch (IOException e){
			e.printStackTrace();
		}finally{
			method.releaseConnection();
		}
		return tgt;
	}

	/**
	 * 通过tgt获取st
	 * @param tgt
	 * @return String
	 */
	public String getST(String tgt){
		String serviceTicket = "";
		HttpClient client = new HttpClient();
		PostMethod method = new PostMethod(get_token_url + tgt + "/");
		method.setRequestBody(new NameValuePair[]{new NameValuePair("service", taget_url)});
		try {
			client.executeMethod(method);
			String response = method.getResponseBodyAsString();
			int status = method.getStatusCode();
			switch (status){
				case HttpStatus.SC_OK: // ok
					serviceTicket = response.toString();
					break;
				default:
					break;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}finally{
			method.releaseConnection();
		}
		return serviceTicket;
	}

}

 

你可能感兴趣的:(java,cas,springmvc,sso单点登录)