(二)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認證鏈的中順序。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM