一、前端实现

1.1、路由守卫(用于拦截路由认证)

import { Injectable, Inject } from "@angular/core";
import {
CanActivate,
ActivatedRouteSnapshot,
RouterStateSnapshot,
Router,
NavigationStart
} from "@angular/router";
import { SessionStorageServiceService } from "./session-storage-service.service";
import { AuthenticateServiceService } from "./authenticate-service.service";
@Injectable()
export class AuthCanActivate implements CanActivate {
// cas认证地址
private casAuthenticateURL = "http://192.1.0.126:8080/dcas-web"; constructor(
private router: Router,
private sessionStorageServiceService: SessionStorageServiceService,
private authenticateServiceService: AuthenticateServiceService
) {}
// 路由守卫
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): boolean {
let paramTicket = this.getQueryString("ticket");
if (paramTicket) {
//cas认证后跳回
let ticketValidator = this.authenticateServiceService.ticketValidator(
paramTicket,
window.location.origin + window.location.pathname
);
if (ticketValidator) {
this.sessionStorageServiceService.setTicketToSessionStorage(
paramTicket
);
//把ticket去掉操作
window.location.href = route.routeConfig.path;
}
} else {
let ticket: String = this.sessionStorageServiceService.getTicketToSessionStorage();
if (!ticket) {
ticket = this.authenticateServiceService.getTicket();
if (ticket) {
this.sessionStorageServiceService.setTicketToSessionStorage(ticket);
}
}
if (ticket) {
return true;
}
//需要跳转认证--用angular Api
window.location.href =
this.casAuthenticateURL + "/login?service=" + window.location.href;
}
return false;
}
getQueryString(name: String) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
var r = window.location.search.substr(1).match(reg);
if (r != null) {
//中文转码
return decodeURI(r[2]);
}
return null;
}
}

1.2、拦截器(用于拦截ajax请求)

import { Injectable } from "@angular/core";
import {
HttpEvent,
HttpInterceptor,
HttpHandler,
HttpRequest,
HttpResponse,
HttpHeaderResponse
} from "@angular/common/http";
import { catchError, mergeMap } from "rxjs/operators";
import { ErrorObservable } from "rxjs/observable/ErrorObservable";
import { SessionStorageServiceService } from "./session-storage-service.service";
import { Observable } from "rxjs/Observable"; @Injectable()
export class AutuHttpclientInterceptor implements HttpInterceptor {
constructor(
private sessionStorageServiceService: SessionStorageServiceService
) {}
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
//对任意请求的url,header添加ticket参数
//判断ticket--TODO
const authReq = req.clone({
//url: (req.url + '&ticket='+this.sessionStorageServiceService.getTicketToSessionStorage())
headers: req.headers
.set(
"ticket",
this.sessionStorageServiceService.getTicketToSessionStorage() as string
)
//标识异步请求
.set("X-Requested-With", "XMLHttpRequest")
});
return next.handle(authReq).pipe(
mergeMap((event: any) => {
if (event instanceof HttpResponse) {
//ticket无效,引导用户去认证
if (event.headers.get("ticket") == "INVALID_TICKET") {
this.sessionStorageServiceService.deleteTicketToSessionStorage();
alert("ticket无效,引导用户去认证");
} else if (event instanceof HttpResponse && event.status != 200) {
return ErrorObservable.create(event);
} else if (event.headers.get("ticket")) {
//前端更新ticket
this.sessionStorageServiceService.setTicketToSessionStorage(
event.headers.get("ticket")
);
}
}
//请求成功返回响应
return Observable.create(observer => {
observer.next(event);
});
}),
catchError((res: HttpResponse<any>) => {
//请求失败处理
alert(res.status + "错误,请联系管理员");
console.error(res.status + "错误,请联系管理员");
return ErrorObservable.create(event);
}) as any
);
}
}

1.3、session存取服务

import { Injectable } from "@angular/core";

@Injectable()
export class SessionStorageServiceService {
constructor() {} private RMK_TICKET = "rmk_ticket";
/**
* 设置缓存
* @param key
* @param obj
*/
public setTicketToSessionStorage(ticket: String): void {
sessionStorage.setItem(this.RMK_TICKET, ticket as string);
}
/**
*
* @param key 获取缓存
*/
public getTicketToSessionStorage(): String {
return sessionStorage.getItem(this.RMK_TICKET) as String;
}
/**
* 删除ticket
*/
public deleteTicketToSessionStorage(): void {
sessionStorage.removeItem(this.RMK_TICKET);
}
}

  1.4、获取、认证ticket前端服务

import { Injectable } from "@angular/core";
import { ajax } from "rxjs/ajax";
@Injectable()
export class AuthenticateServiceService {
constructor() {} // 获取ticket
private getTicketUrl = "/apps-web/rest/authenticate/getTicket?1=1";
//认证ticket是否有效
private ticketValidatorUrl = "/apps-web/authenticate/ticketValidator?1=1"; /**
* 同步请求获取ticket
*/
getTicket(): String {
let ticket: String = null;
ajax({
url: this.getTicketUrl,
method: "GET",
async: false,
responseType: "json"
}).subscribe(
res => {
ticket = res.response as String;
},
error => {
console.error(error);
}
);
return ticket;
} ticketValidator(ticket: String, service: String): Boolean {
let ticketValidate: Boolean = false;
ajax({
url:
this.ticketValidatorUrl + "&ticket=" + ticket + "&service=" + service,
method: "GET",
async: false,
responseType: "json"
}).subscribe(
res => {
ticketValidate = res.response as Boolean;
},
error => {
console.error(error);
}
);
return ticketValidate;
}
}

1.5、app.module.ts配置

import { BrowserModule } from "@angular/platform-browser";
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { HttpClientModule, HTTP_INTERCEPTORS } from "@angular/common/http";
import { AppComponent } from "./app.component";
import { HomeComponent } from "./home/home.component";
import { HttpModule } from "@angular/http";
import { AuthCanActivate } from "./auth.can.activate";
import { AutuHttpclientInterceptor } from "./autu-httpclient-interceptor";
import { AuthenticateServiceService } from "./authenticate-service.service";
import { SessionStorageServiceService } from "./session-storage-service.service";
import { WebSocketServiceService } from "./web-socket-service.service";
import { UserInfoServiceService } from "./user-info-service.service"; export const routes: Routes = [
{
path: "home",
component: HomeComponent,
canActivate: [AuthCanActivate]
}
]; @NgModule({
declarations: [AppComponent, HomeComponent],
imports: [
BrowserModule,
HttpModule,
HttpClientModule,
RouterModule.forRoot(routes)
],
providers: [
AuthCanActivate,
AuthenticateServiceService,
SessionStorageServiceService,
WebSocketServiceService,
UserInfoServiceService,
{
provide: HTTP_INTERCEPTORS,
useClass: AutuHttpclientInterceptor,
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule {}

二、后端代码实现

2.1 TicketCodeAuthenticationFilter实现

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.web.CasAuthenticationFilter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import *.security.authentication.TicketCodeAuthenticationToken;
import *.security.web.authentication.TicketServiceAuthenticationDetails; /**
*
* ticket认证拦截器
*
*
*/
public class TicketCodeAuthenticationFilter extends CasAuthenticationFilter { // =================================================================================================== public TicketCodeAuthenticationFilter() {
// 指定当前过滤器处理的请求
// super("/authentication/ticketValidator", "GET");
super.setRequiresAuthenticationRequestMatcher(
new AntPathRequestMatcher("/authenticate/ticketValidator", "GET"));
} @Override
public Authentication attemptAuthentication(final HttpServletRequest request, final HttpServletResponse response)
throws AuthenticationException, IOException {
final String username = CAS_STATELESS_IDENTIFIER;
// 获取ticket
String password = obtainArtifact(request);
String service = obtainService(request);
if (password == null) {
logger.debug("获取认证票据失败!");
password = "";
}
final TicketCodeAuthenticationToken authRequest = new TicketCodeAuthenticationToken(username, password,
service);
authRequest.setDetails(this.buildDetails(service));
return this.getAuthenticationManager().authenticate(authRequest);
} /**
*
* 构造TicketServiceAuthenticationDetails
*
* @param service
* @return
*/
public TicketServiceAuthenticationDetails buildDetails(String service) {
return new TicketServiceAuthenticationDetails(service);
} /**
*
* 获取认证地址
*
* @param request
* @return
*/
protected String obtainService(HttpServletRequest request) {
return request.getParameter(ServiceProperties.DEFAULT_CAS_SERVICE_PARAMETER);
} }

2.2 TicketCodeAuthenticationToken

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;

/**
* 封装ticket登陆Token类 */
public class TicketCodeAuthenticationToken extends UsernamePasswordAuthenticationToken { private static final long serialVersionUID = 1L; public TicketCodeAuthenticationToken(Object principal, Object credentials, String authenticationUrl) {
super(principal, credentials);
this.principal = principal;
this.credentials = credentials;
this.authenticationUrl = authenticationUrl;
} private final Object principal; private Object credentials; // 认证地址
private String authenticationUrl; public Object getCredentials() {
return this.credentials;
} public void setCredentials(Object credentials) {
this.credentials = credentials;
} public Object getPrincipal() {
return this.principal;
} public String getAuthenticationUrl() {
return this.authenticationUrl;
} }

2.3 TicketServiceAuthenticationDetails

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.cas.web.authentication.ServiceAuthenticationDetails; public class TicketServiceAuthenticationDetails implements ServiceAuthenticationDetails { /** 成员变量:TODO 在这里请添加变量serialVersionUID的描述 */
private static final long serialVersionUID = 1L;
/** 静态变量:系统日志 */
private static final Log logger = LogFactory.getLog(TicketServiceAuthenticationDetails.class); private String serviceUrl; /*
* (non-Javadoc)
*
* @see org.springframework.security.cas.web.authentication.
* ServiceAuthenticationDetails#getServiceUrl()
*/
@Override
public String getServiceUrl() {
return serviceUrl;
} public TicketServiceAuthenticationDetails(String serviceUrl) {
super();
this.serviceUrl = serviceUrl;
}
}

2.4 TicketAuthenticationAjaxRequestFilter

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jasig.cas.client.util.AbstractCasFilter;import org.springframework.cache.Cache;
import org.springframework.cache.Cache.ValueWrapper;
import org.springframework.cache.CacheManager;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext; import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element; /**
*
* 前后端分离基于ticket的ajax拦截器
* */
public class TicketAuthenticationAjaxRequestFilter implements Filter {
// 无效票据标识
public static final String INVALID_TICKET = "INVALID_TICKET"; private String excludePaths; private Ehcache cache;
/**
* 要排除的url路径
*/
private String[] excludePathArrays; /*
* (non-Javadoc)
*
* @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
if (!StringUtil.isBlank(excludePaths)) {
excludePathArrays = excludePaths.trim().split(",");
} else {
excludePathArrays = new String[10];
}
} @Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
if (excludePathArrays == null) {
this.init(null);
}
final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
final HttpServletResponse httpServletResponse = (HttpServletResponse) response;
String uri = httpServletRequest.getRequestURI();
String requestType = httpServletRequest.getHeader("X-Requested-With");
// 非ajax请求,直接放行
if (StringUtil.isBlank(requestType)) {
filterChain.doFilter(request, response);
return;
}
// ajax请求不需要拦截的直接放行
if (excludePathArrays != null && excludePathArrays.length > 0 && !StringUtil.isBlank(uri)) {
for (String path : excludePathArrays) {
if (!StringUtil.isBlank(path)) {
if (uri.contains(path)) {
filterChain.doFilter(request, response);
return;
}
}
}
}
String ticket = httpServletRequest.getHeader(ServiceProperties.DEFAULT_CAS_ARTIFACT_PARAMETER);
Authentication sharedAuthentication = null;
if (!StringUtil.isBlank(ticket) && cache != null) {
Element element = cache.get(ticket);
// 取出共享缓存中的认证信息,认证的时候cacheData写入
if (element != null && element.getValue() instanceof Authentication) {
sharedAuthentication = (Authentication) element.getValue();
}
}
SecurityContext securityContext =SecurityContextHolder.getContext();
Authentication sessionAssertion = securityContext.getAuthentication();
if (sessionAssertion == null && sharedAuthentication != null) {
// session中不存在认证信息,且sharedAssertion 不为空,将sharedAssertion
securityContext.setAuthentication(sharedAuthentication);
filterChain.doFilter(request, response);
return;
}
String sessionAssertionTicket = sessionAssertion == null ? null : sessionAssertion.getCredentials().toString();
String sharedAssertionTicket = sharedAuthentication == null ? null
: sharedAuthentication.getCredentials().toString();
/**
* 两个ticket不一致时,设置header,前端跟新ticket TODO
*/
if (StringUtil.isBlank(sessionAssertionTicket) || !sessionAssertionTicket.equals(sharedAssertionTicket)) {
// sharedAssertion不为空,且sharedAssertion与session中的用户不一致
securityContext.setAuthentication(sharedAuthentication);
httpServletResponse.setHeader(ServiceProperties.DEFAULT_CAS_ARTIFACT_PARAMETER, sharedAssertionTicket);
}
if (securityContext.getAuthentication().isAuthenticated()) {
// session中存在认证信息也放行
httpServletResponse.setHeader(ServiceProperties.DEFAULT_CAS_ARTIFACT_PARAMETER, sessionAssertionTicket);
filterChain.doFilter(request, response);
return;
}
// 返回无效票据标识
httpServletResponse.setHeader(ServiceProperties.DEFAULT_CAS_ARTIFACT_PARAMETER, INVALID_TICKET);
return;
} @Override
public void destroy() { } public String[] getExcludePathArrays() {
return excludePathArrays;
} public void setExcludePathArrays(String[] excludePathArrays) {
this.excludePathArrays = excludePathArrays;
} public String getExcludePaths() {
return excludePaths;
} public void setExcludePaths(String excludePaths) {
this.excludePaths = excludePaths;
} public Ehcache getCache() {
return this.cache;
} public void setCache(Ehcache cache) {
this.cache = cache;
}
}

2.5 ReturnAuthenticationSuccessHandler

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jasig.cas.client.util.AbstractCasFilter;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest; import *.client.security.TicketCodeAuthenticationFilter; /**
*
* ticket认证成功处理器 */
public class ReturnAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
protected final Log logger = LogFactory.getLog(this.getClass()); private CacheManager cacheManager; @Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws ServletException, IOException {
clearAuthenticationAttributes(request);
response.getWriter().write("true");
} /**
* Removes temporary authentication-related data which may have been stored
* in the session during the authentication process.
*/
protected final void clearAuthenticationAttributes(HttpServletRequest request) {
HttpSession session = request.getSession(false); if (session == null) {
return;
} session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
} // public void setRequestCache(RequestCache requestCache) {
// this.requestCache = requestCache;
// } public CacheManager getCacheManager() {
return this.cacheManager;
} public void setCacheManager(CacheManager cacheManager) {
this.cacheManager = cacheManager;
} // public RequestCache getRequestCache() {
// return this.requestCache;
// } }

注意:注意过滤、拦截器在spring认证链的中顺序。

(二)Angular+spring-security-cas前后端分离(基于ticket代码实现的更多相关文章

  1. 基于spring security 实现前后端分离项目权限控制

    前后端分离的项目,前端有菜单(menu),后端有API(backendApi),一个menu对应的页面有N个API接口来支持,本文介绍如何基于spring security实现前后端的同步权限控制. ...

  2. 基于 Spring Security 的前后端分离的权限控制系统

    话不多说,入正题.一个简单的权限控制系统需要考虑的问题如下: 权限如何加载 权限匹配规则 登录 1.  引入maven依赖 1 <?xml version="1.0" enc ...

  3. 喜大普奔,两个开源的 Spring Boot + Vue 前后端分离项目可以在线体验了

    折腾了一周的域名备案昨天终于搞定了. 松哥第一时间想到赶紧把微人事和 V 部落部署上去,我知道很多小伙伴已经等不及了. 1. 也曾经上过线 其实这两个项目当时刚做好的时候,我就把它们部署到服务器上了, ...

  4. 两个开源的 Spring Boot + Vue 前后端分离项目

    折腾了一周的域名备案昨天终于搞定了. 松哥第一时间想到赶紧把微人事和 V 部落部署上去,我知道很多小伙伴已经等不及了. 1. 也曾经上过线 其实这两个项目当时刚做好的时候,我就把它们部署到服务器上了, ...

  5. 实战!spring Boot security+JWT 前后端分离架构认证登录!

    大家好,我是不才陈某~ 认证.授权是实战项目中必不可少的部分,而Spring Security则将作为首选安全组件,因此陈某新开了 <Spring Security 进阶> 这个专栏,写一 ...

  6. Spring Boot + Vue 前后端分离开发,权限管理的一点思路

    在传统的前后端不分的开发中,权限管理主要通过过滤器或者拦截器来进行(权限管理框架本身也是通过过滤器来实现功能),如果用户不具备某一个角色或者某一个权限,则无法访问某一个页面. 但是在前后端分离中,页面 ...

  7. 如何使用Spring Securiry实现前后端分离项目的登录功能

    如果不是前后端分离项目,使用SpringSecurity做登录功能会很省心,只要简单的几项配置,便可以轻松完成登录成功失败的处理,当访问需要认证的页面时,可以自动重定向到登录页面.但是前后端分离的项目 ...

  8. Keycloak快速上手指南,只需10分钟即可接入Spring Boot/Vue前后端分离应用实现SSO单点登录

    登录及身份认证是现代web应用最基本的功能之一,对于企业内部的系统,多个系统往往希望有一套SSO服务对企业用户的登录及身份认证进行统一的管理,提升用户同时使用多个系统的体验,Keycloak正是为此种 ...

  9. Angular企业级开发(9)-前后端分离之后添加验证码

    1.背景介绍 团队开发的项目,前端基于Bootstrap+AngularJS,后端Spring MVC以RESTful接口给前端调用.开发和部署都是前后端分离.项目简单部署图如下,因为后台同时采用微服 ...

  10. 一个实际的案例介绍Spring Boot + Vue 前后端分离

    介绍 最近在工作中做个新项目,后端选用Spring Boot,前端选用Vue技术.众所周知现在开发都是前后端分离,本文就将介绍一种前后端分离方式. 常规的开发方式 采用Spring Boot 开发项目 ...

随机推荐

  1. mac上的mysql管理工具sequel pro

    https://blog.csdn.net/wan_zaiyunduan/article/details/54909389 以前用过Plsql.Navicat.Workbench,现在换到mac上,用 ...

  2. js常见错误类型及chrome常见报错(更新中)

    ECMA-262 定义了下列 7 种错误类型: 1.Error 错误 2.EvalError 全局错误 eval函数没有正确执行 3.RangeError 范围错误 4.ReferenceError ...

  3. pycharm下的多个python版本共存(一)

    经历过IDLE,anaconda,和pycharn的编程环境,并进行了一段时间的项目编程后,决定使用pycharm作为以后的工作环境. 一方面因为项目组其他人推荐,另一方面在使用过程中比较顺手.当然很 ...

  4. 解决vscode电脑卡顿问题

    在安装了vscode之后,发现电脑很卡,cpu使用率高达100%. 解决方法1: 打开vscode之后,点击文件==>首选项==>设置搜索设置search.followSymlinks然后 ...

  5. ThinkPHP 模版中的内置标签

    内置标签就是模版引擎提供的一组可以完成控制.循环和判断功能的类似HTML语法的标签.   一.判断比较:   1.if标签进行条件判断 //if语句的完整格式 <if condition=&qu ...

  6. P1100 三连击

    题目描述 我们假设一个三位整数 \(N(100 \le N \le 999)\) ,它的百位上的数字是 \(A\) ,十位上的数字是 \(B\) ,个位上的数字是 \(C\) ,如果 \(A\) , ...

  7. 2019-1-27-WPF-使用-ItemsPanel-修改方向

    title author date CreateTime categories WPF 使用 ItemsPanel 修改方向 lindexi 2019-1-27 21:8:9 +0800 2019-0 ...

  8. 递归实现深拷贝( 只要学过js递归,看不懂找我包会 )

    要用递归实现深拷贝,首先说说什么是深拷贝和浅拷贝 浅拷贝:一个值赋给另一个值,当原先的值不改变地址的情况下改变数据,另一个值跟着变 深拷贝:一个值赋给另一个值,当原先的值不改变地址的情况下改变数据,另 ...

  9. 2019-9-3-win10-uwp-收集-DUMP-文件

    title author date CreateTime categories win10 uwp 收集 DUMP 文件 lindexi 2019-09-03 17:48:44 +0800 2018- ...

  10. 微信里首次跳转会到首页问题(window.location失效)

    将window.location.href 换为location.href