一、前端實現
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; // } }
