cas單點(diǎn)登錄與springboot關(guān)聯(lián)使用

wallhaven-226884.jpg

1.cas單點(diǎn)登錄介紹

單點(diǎn)登錄( Single Sign-On , 簡(jiǎn)稱 SSO )是目前比較流行的服務(wù)于企業(yè)業(yè)務(wù)整合的解決方案之一, SSO 使得在多個(gè)應(yīng)用系統(tǒng)中磁椒,用戶只需要 登錄一次 就可以訪問所有相互信任的應(yīng)用系統(tǒng)。

從結(jié)構(gòu)體系看长豁, CAS 包括兩部分: CAS Server 和 CAS Client 算撮。

CAS Server
CAS Server 負(fù)責(zé)完成對(duì)用戶的認(rèn)證工作 , 需要獨(dú)立部署 , CAS Server 會(huì)處理用戶名 / 密碼等憑證(Credentials) 。

CAS Client
負(fù)責(zé)處理對(duì)客戶端受保護(hù)資源的訪問請(qǐng)求萍鲸,需要對(duì)請(qǐng)求方進(jìn)行身份認(rèn)證時(shí),重定向到 CAS Server 進(jìn)行認(rèn)證擦俐。(原則上脊阴,客戶端應(yīng)用不再接受任何的用戶名密碼等 Credentials )。

CAS Client 與受保護(hù)的客戶端應(yīng)用部署在一起蚯瞧,以 Filter 方式保護(hù)受保護(hù)的資源嘿期。

2.cas下載編譯

jasig cas下載官網(wǎng)地址 https://www.apereo.org/projects/cas
github項(xiàng)目下載地址 https://github.com/apereo/cas/releases

我使用下載的是cas-4.1.10是maven構(gòu)建的項(xiàng)目,如果你要下cas-4.2以上的版本可能就是gradle構(gòu)建的項(xiàng)目埋合,如果你常用的是gradle备徐,那就下載cas-4.2以上的版本,這里比較坑的就是cas的編譯甚颂。

將下載的項(xiàng)目解壓后蜜猾,進(jìn)入到項(xiàng)目目錄里使用mvn clean install 進(jìn)行編譯,這里會(huì)等待很時(shí)間振诬,如果你沒用翻墻可能有些jar包下載不下來瓣铣,如果編譯不了就在網(wǎng)上找別人編譯后的項(xiàng)目,我在編譯的時(shí)候就一直編譯不了贷揽,下載太慢,坑了我很長時(shí)間梦碗。

編譯后將cas-server-webapp項(xiàng)目下的targer的war包放到tomcat的里禽绪,啟動(dòng)tomcat蓖救。這里的詳細(xì)步驟可以參考
Jasig cas 單點(diǎn)登錄系統(tǒng)Server&Java Client配置

這里的搭建cas server一端就不詳細(xì)說了,下面主要說說與spring boot的結(jié)合配置

3.spring boot的配置

這里需要你有一個(gè)spring boot的項(xiàng)目印屁,如何搭建可以參考網(wǎng)上的教程循捺。

引入jar包

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-cas</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
        </dependency>

有過spring security的經(jīng)驗(yàn)的應(yīng)該知道關(guān)于權(quán)限登陸的用法 ,實(shí)現(xiàn)AuthenticationUserDetailsService 接口或UserDetailsService 接口雄人,用作登陸驗(yàn)證从橘。

import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import java.util.ArrayList;
import java.util.List;

/**
 * Authenticate a user from the database.
 */
public class CustomUserDetailsService implements AuthenticationUserDetailsService<CasAssertionAuthenticationToken> {

    @Override
    public UserDetails loadUserDetails(CasAssertionAuthenticationToken token) throws UsernameNotFoundException {
        String login = token.getPrincipal().toString();
        String username = login.toLowerCase();

        List<GrantedAuthority> grantedAuthorities = new ArrayList<GrantedAuthority>();
        grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));

        return new AppUserDetails(username, grantedAuthorities);
    }

}

然后就用到了userDetails 接口,我們這里實(shí)現(xiàn)這個(gè)接口础钠,并定義一些關(guān)于用戶的信息和權(quán)限恰力。如果你在clien要使用這些信息,比如用戶名旗吁,角色踩萎,權(quán)限等信息,你可以在這里處理一下很钓。

package com.shang.spray.security;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

public class AppUserDetails implements UserDetails {

    /** */
    private static final long serialVersionUID = -4777124807325532850L;

    private String username;

    private String password;

    private boolean accountNonExpired;

    private boolean accountNonLocked;

    private boolean credentialsNonExpired;

    private boolean enabled;

    private Collection<? extends GrantedAuthority> authorities;

    private List<String> roles;

    public AppUserDetails() {
        super();
    }

    public AppUserDetails(String username, Collection<? extends GrantedAuthority> authorities) {
        super();
        this.username = username;
        this.password = "";
        this.accountNonExpired = true;
        this.accountNonLocked = true;
        this.credentialsNonExpired = true;
        this.enabled = true;
        this.authorities = authorities;
        this.roles = new ArrayList<>();
        this.roles.addAll(authorities.stream().map((Function<GrantedAuthority, String>) GrantedAuthority::getAuthority).collect(Collectors.toList()));
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        /*
         * List<GrantedAuthority> l = new ArrayList<GrantedAuthority>(); l.add(new
         * GrantedAuthority() { private static final long serialVersionUID = 1L;
         * 
         * @Override public String getAuthority() { return "ROLE_AUTHENTICATED"; } }); return l;
         */
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return accountNonExpired;
    }

    @Override
    public boolean isAccountNonLocked() {
        return accountNonLocked;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return credentialsNonExpired;
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }


}

接下來就是最重要的一個(gè)步驟香府,繼承實(shí)現(xiàn)WebSecurityConfigurerAdapter 配置類,并配置關(guān)于單點(diǎn)登錄服務(wù)器的一些信息和攔截頁面码倦。這里先放上這個(gè)實(shí)現(xiàn)類

package com.shang.spray.security;

import org.jasig.cas.client.session.SingleSignOutFilter;
import org.jasig.cas.client.validation.Cas20ServiceTicketValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken;
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
import org.springframework.security.cas.web.CasAuthenticationFilter;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.inject.Inject;

@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    private static final String CAS_URL_LOGIN = "cas.service.login";
    private static final String CAS_URL_LOGOUT = "cas.service.logout";
    private static final String CAS_URL_PREFIX = "cas.url.prefix";
    private static final String CAS_SERVICE_URL = "app.service.security";
    private static final String APP_SERVICE_HOME = "app.service.home";

    @Inject
    private Environment env;


    @Bean
    public ServiceProperties serviceProperties() {
        ServiceProperties sp = new ServiceProperties();
        sp.setService(env.getRequiredProperty(CAS_SERVICE_URL));
        sp.setSendRenew(false);
        return sp;
    }

    @Bean
    public CasAuthenticationProvider casAuthenticationProvider() {
        CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider();
        casAuthenticationProvider.setAuthenticationUserDetailsService(customUserDetailsService());
        casAuthenticationProvider.setServiceProperties(serviceProperties());
        casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator());
        casAuthenticationProvider.setKey("an_id_for_this_auth_provider_only");
        return casAuthenticationProvider;
    }

    @Bean
    public AuthenticationUserDetailsService<CasAssertionAuthenticationToken> customUserDetailsService() {
        return new CustomUserDetailsService();
    }

    @Bean
    public Cas20ServiceTicketValidator cas20ServiceTicketValidator() {
        return new Cas20ServiceTicketValidator(env.getRequiredProperty(CAS_URL_PREFIX));
    }

    @Bean
    public CasAuthenticationFilter casAuthenticationFilter() throws Exception {
        CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter();
        casAuthenticationFilter.setAuthenticationManager(authenticationManager());
        casAuthenticationFilter.setFilterProcessesUrl("/j_spring_cas_security_check");
        return casAuthenticationFilter;
    }

    @Bean
    public CasAuthenticationEntryPoint casAuthenticationEntryPoint() {
        CasAuthenticationEntryPoint casAuthenticationEntryPoint = new CasAuthenticationEntryPoint();
        casAuthenticationEntryPoint.setLoginUrl(env.getRequiredProperty(CAS_URL_LOGIN));
        casAuthenticationEntryPoint.setServiceProperties(serviceProperties());
        return casAuthenticationEntryPoint;
    }

    @Bean
    public SingleSignOutFilter singleSignOutFilter() {
        SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
        singleSignOutFilter.setCasServerUrlPrefix(env.getRequiredProperty(CAS_URL_PREFIX));
        return singleSignOutFilter;
    }

    @Bean
    public LogoutFilter requestCasGlobalLogoutFilter() {
        LogoutFilter logoutFilter = new LogoutFilter(env.getRequiredProperty(CAS_URL_LOGOUT) + "?service="
                + env.getRequiredProperty(APP_SERVICE_HOME), new SecurityContextLogoutHandler());
        logoutFilter.setLogoutRequestMatcher(new AntPathRequestMatcher("/logout", "POST"));
        return logoutFilter;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(casAuthenticationProvider());
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilter(casAuthenticationFilter());
        http.exceptionHandling().authenticationEntryPoint(casAuthenticationEntryPoint());
        http.addFilterBefore(singleSignOutFilter(), CasAuthenticationFilter.class)
                .addFilterBefore(requestCasGlobalLogoutFilter(), LogoutFilter.class);

        http.csrf().disable();
        http.headers().frameOptions().disable();
        http.authorizeRequests().antMatchers("/assets/**").permitAll()
                .anyRequest().authenticated();

        http.logout().logoutUrl("/logout").logoutSuccessUrl("/").invalidateHttpSession(true)
                .deleteCookies("JSESSIONID");
    }
}

配置信息

#cas
app.service.security=http://localhost:8200/j_spring_cas_security_check
app.service.home=http://localhost:8200

cas.service.login=http://localhost:8080/cas/login
cas.service.logout=http://localhost:8080/cas/logout
cas.url.prefix=http://localhost:8080/cas

關(guān)于上面的一些說明:

  1. serviceProperties單點(diǎn)登錄服務(wù)器地址
  2. casAuthenticationProvider 權(quán)限登錄配置
  3. 客戶端登錄地址和退出地址的配置
  4. 客戶端攔截的路徑配置
  5. 配置后需要啟用配置@Configuration@EnableWebSecurity

新更新內(nèi)容

上面的版本有些問題企孩,我這里做了下新修改。
項(xiàng)目地址為:https://github.com/shangjing105/spray-module
cas項(xiàng)目為里面的spray-manage-cas與cas服務(wù)端spray-cas

j_spring_cas_security_check 是每個(gè)項(xiàng)目客戶端spring secrity驗(yàn)證ticket的地址袁稽,spring secrity版本4.0以下的是用j_spring_cas_security_check這個(gè)地址勿璃,4.0以上的用的是/login/cas地址,配置的時(shí)候注意項(xiàng)目版本运提。

//因?yàn)槲矣玫腸as版本是4.0以上蝗柔,所以修改為/login/cas
app.service.security=http://localhost:8400/login/cas
app.service.home=http://localhost:8400

cas.service.login=http://localhost:8080/login
cas.service.logout=http://localhost:8080/logout
cas.url.prefix=http://localhost:8080
    @Bean
    public CasAuthenticationFilter casAuthenticationFilter() throws Exception {
        CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter();
        casAuthenticationFilter.setAuthenticationManager(authenticationManager());
        //這里給上面的配置去掉,因?yàn)槟J(rèn)就是/login/cas民泵,可以不用配置癣丧。如果是4.0以前就是j_spring_cas_security_check
        return casAuthenticationFilter;
    }

    @Bean
    public SingleSignOutFilter singleSignOutFilter() {
        SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
        //這里增加一個(gè)條件,不然啟動(dòng)時(shí)會(huì)出錯(cuò):casserverurlprefix cannot be null
        singleSignOutFilter.setIgnoreInitConfiguration(true);
        singleSignOutFilter.setCasServerUrlPrefix(env.getRequiredProperty(CAS_URL_PREFIX));
        return singleSignOutFilter;
    }

4.結(jié)束

以上單點(diǎn)登錄cas與spring boot的配置使用栈妆,對(duì)于有多個(gè)后臺(tái)的系統(tǒng)胁编,寫一個(gè)單獨(dú)的單點(diǎn)登錄系統(tǒng)來對(duì)所有的平臺(tái)來管理感覺非常有必要。 沒有使用過的可以去嘗試一下鳞尔,簡(jiǎn)單好用你值的一學(xué)嬉橙。

有什么問題歡迎給我來信或留言!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末寥假,一起剝皮案震驚了整個(gè)濱河市市框,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌糕韧,老刑警劉巖枫振,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件喻圃,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡粪滤,警方通過查閱死者的電腦和手機(jī)斧拍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來杖小,“玉大人肆汹,你說我怎么就攤上這事∮枞ǎ” “怎么了昂勉?”我有些...
    開封第一講書人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長伟件。 經(jīng)常有香客問我硼啤,道長,這世上最難降的妖魔是什么斧账? 我笑而不...
    開封第一講書人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任谴返,我火速辦了婚禮,結(jié)果婚禮上咧织,老公的妹妹穿的比我還像新娘嗓袱。我一直安慰自己,他們只是感情好习绢,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開白布渠抹。 她就那樣靜靜地躺著,像睡著了一般闪萄。 火紅的嫁衣襯著肌膚如雪梧却。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評(píng)論 1 299
  • 那天败去,我揣著相機(jī)與錄音放航,去河邊找鬼。 笑死圆裕,一個(gè)胖子當(dāng)著我的面吹牛广鳍,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播吓妆,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼赊时,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了行拢?” 一聲冷哼從身側(cè)響起祖秒,我...
    開封第一講書人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后竭缝,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體狐胎,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年歌馍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晕鹊。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡松却,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出溅话,到底是詐尸還是另有隱情晓锻,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布飞几,位于F島的核電站砚哆,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏屑墨。R本人自食惡果不足惜躁锁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望卵史。 院中可真熱鬧战转,春花似錦、人聲如沸以躯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽忧设。三九已至刁标,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間址晕,已是汗流浹背膀懈。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留斩箫,地道東北人吏砂。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像乘客,于是被迫代替她去往敵國和親狐血。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理易核,服務(wù)發(fā)現(xiàn)匈织,斷路器,智...
    卡卡羅2017閱讀 134,652評(píng)論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,806評(píng)論 6 342
  • JEESZ分布式框架單點(diǎn)登錄集成方案 第一節(jié):?jiǎn)吸c(diǎn)登錄簡(jiǎn)介 第一步:了解單點(diǎn)登錄 SSO主要特點(diǎn)是: SSO應(yīng)用之...
    ITsupuerlady閱讀 671評(píng)論 0 6
  • JEESZ分布式框架單點(diǎn)登錄集成方案 第一節(jié):?jiǎn)吸c(diǎn)登錄簡(jiǎn)介 第一步:了解單點(diǎn)登錄 SSO主要特點(diǎn)是: SSO應(yīng)用之...
    ITsupuerlady閱讀 698評(píng)論 0 0
  • 話說 獅子 胡99 2017-04-13 獅子的概念從小就有了,因?yàn)樾r(shí)候過春節(jié)缀匕,同姓氏人集中的...
    99閱讀 397評(píng)論 2 5