一、前端实现
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; // } }
