(二)Angular+spring-security-cas前后端分离(基于ticket代码实现
一、前端实现
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代码实现的更多相关文章
- 基于spring security 实现前后端分离项目权限控制
前后端分离的项目,前端有菜单(menu),后端有API(backendApi),一个menu对应的页面有N个API接口来支持,本文介绍如何基于spring security实现前后端的同步权限控制. ...
- 基于 Spring Security 的前后端分离的权限控制系统
话不多说,入正题.一个简单的权限控制系统需要考虑的问题如下: 权限如何加载 权限匹配规则 登录 1. 引入maven依赖 1 <?xml version="1.0" enc ...
- 喜大普奔,两个开源的 Spring Boot + Vue 前后端分离项目可以在线体验了
折腾了一周的域名备案昨天终于搞定了. 松哥第一时间想到赶紧把微人事和 V 部落部署上去,我知道很多小伙伴已经等不及了. 1. 也曾经上过线 其实这两个项目当时刚做好的时候,我就把它们部署到服务器上了, ...
- 两个开源的 Spring Boot + Vue 前后端分离项目
折腾了一周的域名备案昨天终于搞定了. 松哥第一时间想到赶紧把微人事和 V 部落部署上去,我知道很多小伙伴已经等不及了. 1. 也曾经上过线 其实这两个项目当时刚做好的时候,我就把它们部署到服务器上了, ...
- 实战!spring Boot security+JWT 前后端分离架构认证登录!
大家好,我是不才陈某~ 认证.授权是实战项目中必不可少的部分,而Spring Security则将作为首选安全组件,因此陈某新开了 <Spring Security 进阶> 这个专栏,写一 ...
- Spring Boot + Vue 前后端分离开发,权限管理的一点思路
在传统的前后端不分的开发中,权限管理主要通过过滤器或者拦截器来进行(权限管理框架本身也是通过过滤器来实现功能),如果用户不具备某一个角色或者某一个权限,则无法访问某一个页面. 但是在前后端分离中,页面 ...
- 如何使用Spring Securiry实现前后端分离项目的登录功能
如果不是前后端分离项目,使用SpringSecurity做登录功能会很省心,只要简单的几项配置,便可以轻松完成登录成功失败的处理,当访问需要认证的页面时,可以自动重定向到登录页面.但是前后端分离的项目 ...
- Keycloak快速上手指南,只需10分钟即可接入Spring Boot/Vue前后端分离应用实现SSO单点登录
登录及身份认证是现代web应用最基本的功能之一,对于企业内部的系统,多个系统往往希望有一套SSO服务对企业用户的登录及身份认证进行统一的管理,提升用户同时使用多个系统的体验,Keycloak正是为此种 ...
- Angular企业级开发(9)-前后端分离之后添加验证码
1.背景介绍 团队开发的项目,前端基于Bootstrap+AngularJS,后端Spring MVC以RESTful接口给前端调用.开发和部署都是前后端分离.项目简单部署图如下,因为后台同时采用微服 ...
- 一个实际的案例介绍Spring Boot + Vue 前后端分离
介绍 最近在工作中做个新项目,后端选用Spring Boot,前端选用Vue技术.众所周知现在开发都是前后端分离,本文就将介绍一种前后端分离方式. 常规的开发方式 采用Spring Boot 开发项目 ...
随机推荐
- POJ2185 Milking Grid 题解 KMP算法
题目链接:http://poj.org/problem?id=2185 题目大意:求一个二维的字符串矩阵的最小覆盖子矩阵,即这个最小覆盖子矩阵在二维空间上不断翻倍后能覆盖原始矩阵. 题目分析:next ...
- CSS优化,提高性能的方法有哪些?
1,首推的是合并css文件,如果页面加载10个css文件,每个文件1k,那么也要比只加载一个100k的css文件慢. 2,减少css嵌套,最好不要套三层以上. 3,不要在ID选择器前面进行嵌套,ID本 ...
- 5分钟了解为什么学习Go
1.什么是Go语言? Google开源 编译型语言 21世纪的C语言(主流编程语言都是单线程环境下发布的) 2.Go语言的特点? 简单易学习(类似python学习难度,自带格式化) 开发效率高 执行性 ...
- 极简触感反馈Button组件
一个简单的React触感反馈的button组件 import React from 'react'; import './index.scss'; class Button extends React ...
- C#的类
一.String类 1.Length 字符的长度 string x = Console.ReadLine();int i = x.Length;// Length 是获取字符串的长度(从1开始数)Co ...
- 洛谷P2258 子矩阵 题解 状态压缩/枚举/动态规划
作者:zifeiy 标签:状态压缩.枚举.动态规划 题目链接:https://www.luogu.org/problem/P2258 这道题目状态压缩是肯定的,我们需要用二进制来枚举状态. 江湖上有一 ...
- 搭建服务器上的GIT并实现自动同步到站点目录(www)
https://blog.csdn.net/baidu_30000217/article/details/51327289 前言:当我们想要实现几个小伙伴合作开发同一个项目,或者建立一个资源分享平台的 ...
- python3在pycharm中为什么导入random模块不能用? TypeError: 'module' object is not callable
新手学python求大神指导,也用sys导入了random.py的路径,仍然不行. 刚刚排错貌似找到了问题的原因...那是因为我在pycharm中新建的python文件名就是random,所以当前目录 ...
- Python--day46--mysql存储过程(不常用)(包含防sql注入)
一.存储过程: 优点:只要传很少的数据到数据库就可以了 缺点:dba管理数据库的时候可能会对数据库进行了更改了那一坨sql语句. 二.创建存储过程: 1.简单 创建存储过程: Python中使用结果 ...
- H3C RIPv2配置举例