CAS-技術專區-客戶端集成(shiro springboot jwt pac4j)


使用場景:

  • 移動端通過業務系統鑒權
  • 移動端免登錄(登錄一次以后)

解決方案:

  • JWT(token認證方案)
  • OAuth(第三方認證)

疑問

  當然我們這章是講JWT,那么會有以下的疑問:

  若服務端已經接入了SSO,那么在移動端用戶登錄信息提交給SSO還是服務端?(毫無疑問是服務端,SSO對於移動端必須是透明的)

  若采用無會話方式,如何獲取token,服務端如何鑒權?

1.提交用戶名密碼到服務端,服務端把數據給到sso,sso最終返回用戶數據

2. 根據用戶數據創建token返回給移動端

3. 移動端登錄后請求都帶token給到服務端鑒權

  在使用token鑒權的情況下,退出如何解決?(客戶端丟棄token即可)

鑒權流程

  我們再講一下整一個鑒權流程

配置要素

  重點:sso必須支持rest認證方式 

<dependency>
  <groupId>io.buji</groupId>
  <artifactId>buji-pac4j</artifactId>
  <version>3.0.0</version>
</dependency>
<dependency>
  <groupId>org.pac4j</groupId>
  <artifactId>pac4j-cas</artifactId>
  <version>2.1.0</version>
</dependency>
<dependency>
  <groupId>org.pac4j</groupId>
  <artifactId>pac4j-jwt</artifactId>
  <version>2.1.0</version>
</dependency>
<dependency>
  <groupId>org.pac4j</groupId>
  <artifactId>pac4j-http</artifactId>
  <version>2.1.0</version>
</dependency>

鑒權配置

若馬上看下面的代碼,估計一時半會看不懂,所以必須再講一下整個交易過程

服務端鑒權過程有兩個個角色分別為,Shiro、Pac4j,那他們的職責是什么?

Shiro:

  判斷當前Subject是否有權限執行該資源,所以他的核心是Realm、Filter,只有被過濾到的資源才會走到Realm

Pac4j:

1. JWTAuthenticator對token進行鑒別
2. CasRestFormClient 支持通過rest接口傳入用戶名密碼進行對sso進行認證獲取UserProfile
3. Pac4jRealm鑒權的realm
4. SubjectFactory需要調整成Pac4jSubjectFactory

ShiroConfiguration.java

/*
 * 版權所有.(c)2008-2017. 卡爾科技工作室
 */
package com.carl.wolf.permission.config;
import io.buji.pac4j.filter.CallbackFilter;
import io.buji.pac4j.filter.LogoutFilter;
import io.buji.pac4j.filter.SecurityFilter;
import io.buji.pac4j.realm.Pac4jRealm;
import io.buji.pac4j.subject.Pac4jSubjectFactory;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.mgt.SubjectFactory;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.spring.web.config.AbstractShiroWebFilterConfiguration;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.pac4j.cas.client.CasClient;
import org.pac4j.cas.client.rest.CasRestFormClient;
import org.pac4j.cas.config.CasConfiguration;
import org.pac4j.cas.config.CasProtocol;
import org.pac4j.core.client.Clients;
import org.pac4j.core.config.Config;
import org.pac4j.http.client.direct.ParameterClient;
import org.pac4j.jwt.config.encryption.SecretEncryptionConfiguration;
import org.pac4j.jwt.config.signature.SecretSignatureConfiguration;
import org.pac4j.jwt.credentials.authenticator.JwtAuthenticator;
import org.pac4j.jwt.profile.JwtGenerator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.HashMap;
import java.util.Map;

/**
 * 對shiro的安全配置,是對cas的登錄策略進行配置
 *
 * @author Carl
 * @date 2017/9/16
 * @since 1.0.0
 */
@Configuration
public class ShiroConfiguration extends AbstractShiroWebFilterConfiguration {
    
   @Value("#{ @environment['cas.prefixUrl'] ?: null }") private String prefixUrl; @Value("#{ @environment['cas.loginUrl'] ?: null }") private String casLoginUrl; @Value("#{ @environment['cas.callbackUrl'] ?: null }") private String callbackUrl; //jwt秘鑰 @Value("${jwt.salt}") private String salt; @Bean public Realm pac4jRealm() { return new Pac4jRealm(); } /** * cas核心過濾器,把支持的client寫上,filter過濾時才會處理,clients必須在casConfig.clients已經注冊 * * @return */ @Bean public Filter casSecurityFilter() { SecurityFilter filter = new SecurityFilter(); filter.setClients("CasClient,rest,jwt"); filter.setConfig(casConfig()); return filter; } /** * JWT Token 生成器,對CommonProfile生成然后每次攜帶token訪問 * @return */ @Bean protected JwtGenerator jwtGenerator() { return new JwtGenerator(new SecretSignatureConfiguration(salt), new SecretEncryptionConfiguration(salt)); } /** * 通過rest接口可以獲取tgt,獲取service ticket,甚至可以獲取CasProfile * @return */ @Bean protected CasRestFormClient casRestFormClient() { CasRestFormClient casRestFormClient = new CasRestFormClient(); casRestFormClient.setConfiguration(casConfiguration()); casRestFormClient.setName("rest"); return casRestFormClient; } @Bean protected Clients clients() { //可以設置默認client Clients clients = new Clients(); //token校驗器,可以用HeaderClient更安全 ParameterClient parameterClient = new ParameterClient("token", jwtAuthenticator()); parameterClient.setSupportGetRequest(true); parameterClient.setName("jwt"); //支持的client全部設置進去 clients.setClients(casClient(), casRestFormClient(), parameterClient); return clients; } /** * JWT校驗器,也就是目前設置的ParameterClient進行的校驗器,是rest/或者前后端分離的核心校驗器 * @return */ @Bean protected JwtAuthenticator jwtAuthenticator() { JwtAuthenticator jwtAuthenticator = new JwtAuthenticator(); jwtAuthenticator.addSignatureConfiguration(new SecretSignatureConfiguration(salt)); jwtAuthenticator.addEncryptionConfiguration(new SecretEncryptionConfiguration(salt)); return jwtAuthenticator; } @Bean protected Config casConfig() { Config config = new Config(); config.setClients(clients()); return config; } /** * cas的基本設置,包括或url等等,rest調用協議等 * @return */ @Bean public CasConfiguration casConfiguration() { CasConfiguration casConfiguration = new CasConfiguration(casLoginUrl); casConfiguration.setProtocol(CasProtocol.CAS30); casConfiguration.setPrefixUrl(prefixUrl); return casConfiguration; } @Bean public CasClient casClient() { CasClient casClient = new CasClient(); casClient.setConfiguration(casConfiguration()); casClient.setCallbackUrl(callbackUrl); return casClient; } /** * 路徑過濾設置 * @return */ @Bean public ShiroFilterChainDefinition shiroFilterChainDefinition() { DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition(); definition.addPathDefinition("/callback", "callbackFilter"); definition.addPathDefinition("/logout", "logoutFilter"); definition.addPathDefinition("/**", "casSecurityFilter"); return definition; } /** * 由於cas代理了用戶,所以必須通過cas進行創建對象 * * @return */ @Bean protected SubjectFactory subjectFactory() { return new Pac4jSubjectFactory(); } /** * 對過濾器進行調整 * * @param securityManager * @return */ @Bean protected ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { //把subject對象設為subjectFactory ((DefaultSecurityManager) securityManager).setSubjectFactory(subjectFactory()); ShiroFilterFactoryBean filterFactoryBean = super.shiroFilterFactoryBean(); filterFactoryBean.setSecurityManager(securityManager); filterFactoryBean.setFilters(filters()); return filterFactoryBean; } /** * 對shiro的過濾策略進行明確 * @return */ @Bean protected Map<String, Filter> filters() { //過濾器設置 Map<String, Filter> filters = new HashMap<>(); filters.put("casSecurityFilter", casSecurityFilter()); CallbackFilter callbackFilter = new CallbackFilter(); callbackFilter.setConfig(casConfig()); filters.put("callbackFilter", callbackFilter); LogoutFilter logoutFilter = new LogoutFilter(); logoutFilter.setConfig(casConfig()); filters.put("logoutFilter", logoutFilter); return filters; } }

token生成

 
        
@RequestMapping("/user/login")
public Object login(HttpServletRequest request, HttpServletResponse response) {
    Map<String, Object> model = new HashMap<>();
    J2EContext context = new J2EContext(request, response);
    final ProfileManager<CasRestProfile> manager = new ProfileManager(context);
    final Optional<CasRestProfile> profile = manager.get(true);
    //獲取ticket
    TokenCredentials tokenCredentials = casRestFormClient.requestServiceTicket(serviceUrl, profile.get(), context);
    //根據ticket獲取用戶信息
    final CasProfile casProfile = casRestFormClient.validateServiceTicket(serviceUrl, tokenCredentials, context);
    //生成jwt token
    String token = generator.generate(casProfile);
    model.put("token", token);
    return new HttpEntity<>(model);
}

 


免責聲明!

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



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