一、为什么要使用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 extends Object> 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;
}
}