一、前端实现
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; // } }
(二)Angular+spring-security-cas前后端分离(基于ticket代码实现
原文:https://www.cnblogs.com/lijjingchuan/p/12162868.html