SpringBoot與Spring-Security的集成

Spring Security簡(jiǎn)介

Spring Security是一個(gè)功能強(qiáng)大且可高度自定義的身份驗(yàn)證和訪問(wèn)控制框架切厘。保護(hù)基于Spring的應(yīng)用程序纷妆。Spring Security是一個(gè)專注于為Java應(yīng)用程序提供身份驗(yàn)證和授權(quán)的框架铺呵。與所有Spring項(xiàng)目一樣崖蜜,Spring Security的真正強(qiáng)大之處在于它可以輕松擴(kuò)展以滿足自定義要求
Github地址:https://github.com/spring-projects/spring-security
開發(fā)文檔:
https://docs.spring.io/spring-security/site/docs/5.1.2.RELEASE/reference/htmlsingle/
API文檔:
https://docs.spring.io/spring-security/site/docs/5.1.2.RELEASE/api/

特點(diǎn):

1.對(duì)身份驗(yàn)證和授權(quán)的全面和可擴(kuò)展的支持
2.防止會(huì)話固定己儒,點(diǎn)擊劫持誓军,跨站點(diǎn)請(qǐng)求偽造等攻擊
3.Servlet API集成
4.可與Spring Web MVC集成

與SpringBoot進(jìn)行集成

引入依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>   

測(cè)試接口

@RestController
public class controller {
    @RequestMapping("/")
    public String home() {
        return "hello spring boot";
    }

    @RequestMapping("/hello")
    public String hello() {
        return "hello world";
    }
}

開啟提供基于web的Security配置

@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/").permitAll()
                .anyRequest().authenticated()
                .and()
                .logout().permitAll()
                .and()
                .formLogin();
        http.csrf().disable();
    }
}

@EnableWebMvcSecurity:開啟Spring Security的功能
WebSecurityConfigurerAdapter:重寫里面的方法來(lái)設(shè)置一些web安全的細(xì)節(jié)(主要通過(guò)重寫configure())

WebSecurityConfigurerAdapterconfigure方法的源碼
public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigurer<WebSecurity> {
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        this.disableLocalConfigureAuthenticationBldr = true;
    }
    public void configure(WebSecurity web) throws Exception {
    }

    protected void configure(HttpSecurity http) throws Exception {
        this.logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
        ((HttpSecurity)((HttpSecurity)((AuthorizedUrl)http.authorizeRequests().anyRequest()).authenticated().and()).formLogin().and()).httpBasic();
    }
    ......
}

configure(AuthenticationManagerBuilder auth)配置在內(nèi)存中進(jìn)行注冊(cè)公開內(nèi)存的身份驗(yàn)證
configure(WebSecurity web)配置攔截資源,例如過(guò)濾掉css/js/images等靜態(tài)資源
configure(HttpSecurity http)定義需要攔截的URL

HttpSecurity具體使用可以參考
https://blog.csdn.net/dawangxiong123/article/details/68960041

接下來(lái)啟動(dòng)測(cè)試下
訪問(wèn)http://localhost:8080/ 沒問(wèn)題同蜻,因?yàn)榕渲昧?code>antMatchers("/")

image.png

訪問(wèn)http://localhost:8080/hello 就提示需要登錄

image.png

配置基于內(nèi)存的身份驗(yàn)證

重寫configure(AuthenticationManagerBuilder auth)方法棚点,設(shè)置用戶名和密碼還有角色

    @Bean
    public BCryptPasswordEncoder PasswordEncoder(){
        return new BCryptPasswordEncoder();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
         auth.inMemoryAuthentication()
             .withUser("admin")
             .password(PasswordEncoder().encode("123456"))
             .roles("ADMIN");
    }

注:SpringBoot2.x后需要使用BCrypt強(qiáng)哈希方法來(lái)加密密碼,如果不加的話登錄不上并且控制臺(tái)會(huì)有警告Encoded password does not look like BCrypt

再次訪問(wèn)http://localhost:8080/hello 湾蔓,輸入設(shè)置好的用戶名和密碼

image.png

根據(jù)角色做接口權(quán)限設(shè)置

在啟動(dòng)類上加入@EnableGlobalMethodSecurity(prePostEnabled = true)來(lái)實(shí)現(xiàn)授權(quán),實(shí)現(xiàn)角色對(duì)某個(gè)操作是否有權(quán)限的控制.
@EnableGlobalMethodSecurity源碼
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({GlobalMethodSecuritySelector.class})
@EnableGlobalAuthentication
@Configuration
public @interface EnableGlobalMethodSecurity {
    boolean prePostEnabled() default false;
    boolean securedEnabled() default false;
    boolean jsr250Enabled() default false;
    boolean proxyTargetClass() default false;
    AdviceMode mode() default AdviceMode.PROXY;
    int order() default 2147483647;
}
Spring Security默認(rèn)是禁用注解的

1.prePostEnabled:支持Spring EL表達(dá)式瘫析,開啟后可以使用
@PreAuthorize:方法執(zhí)行前的權(quán)限驗(yàn)證
@PostAuthorize:方法執(zhí)行后再進(jìn)行權(quán)限驗(yàn)證
@PreFilter:方法執(zhí)行前對(duì)集合類型的參數(shù)或返回值進(jìn)行過(guò)濾,移除使對(duì)應(yīng)表達(dá)式的結(jié)果為false的元素
@PostFilter:方法執(zhí)行后對(duì)集合類型的參數(shù)或返回值進(jìn)行過(guò)濾默责,移除使對(duì)應(yīng)表達(dá)式的結(jié)果為false的元素

2.secureEnabled : 開啟后可以使用
@Secured:用來(lái)定義業(yè)務(wù)方法的安全性配置屬性列表

3.jsr250Enabled :支持JSR標(biāo)準(zhǔn)贬循,開啟后可以使用
@RolesAllowed:對(duì)方法進(jìn)行角色驗(yàn)證
@DenyAll:允許所有角色調(diào)用
@PermitAll:不允許允許角色調(diào)用

controller層做相應(yīng)的配置
@RestController
public class controller {
    @RequestMapping("/")
    public String home() {
        return "hello spring boot";
    }

    @RequestMapping("/hello")
    public String hello() {
        return "hello world";
    }
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    @RequestMapping("/roleAuth")
    public String role() {
        return "admin auth";
    }
}

@PreAuthorize("hasRole('ROLE_ADMIN')"):訪問(wèn)前對(duì)角色做校驗(yàn),只有ADMIN的角色才能訪問(wèn)

Spring-Security基于表達(dá)式的權(quán)限控制

Spring Security允許我們?cè)诙xURL訪問(wèn)或方法訪問(wèn)所應(yīng)有的權(quán)限時(shí)使用Spring EL表達(dá)式
Spring Security可用表達(dá)式對(duì)象的基類是SecurityExpressionRoot

SecurityExpressionRoot源碼
public abstract class SecurityExpressionRoot implements SecurityExpressionOperations {
    protected final Authentication authentication;
    private AuthenticationTrustResolver trustResolver;
    private RoleHierarchy roleHierarchy;
    private Set<String> roles;
    private String defaultRolePrefix = "ROLE_";
    public final boolean permitAll = true;
    public final boolean denyAll = false;
    private PermissionEvaluator permissionEvaluator;
    public final String read = "read";
    public final String write = "write";
    public final String create = "create";
    public final String delete = "delete";
    public final String admin = "administration";

    public SecurityExpressionRoot(Authentication authentication) {
        if (authentication == null) {
            throw new IllegalArgumentException("Authentication object cannot be null");
        } else {
            this.authentication = authentication;
        }
    }

    public final boolean hasAuthority(String authority) {
        return this.hasAnyAuthority(authority);
    }

    public final boolean hasAnyAuthority(String... authorities) {
        return this.hasAnyAuthorityName((String)null, authorities);
    }

    public final boolean hasRole(String role) {
        return this.hasAnyRole(role);
    }

    public final boolean hasAnyRole(String... roles) {
        return this.hasAnyAuthorityName(this.defaultRolePrefix, roles);
    }

    private boolean hasAnyAuthorityName(String prefix, String... roles) {
        Set<String> roleSet = this.getAuthoritySet();
        String[] var4 = roles;
        int var5 = roles.length;

        for(int var6 = 0; var6 < var5; ++var6) {
            String role = var4[var6];
            String defaultedRole = getRoleWithDefaultPrefix(prefix, role);
            if (roleSet.contains(defaultedRole)) {
                return true;
            }
        }

        return false;
    }

    public final Authentication getAuthentication() {
        return this.authentication;
    }

    public final boolean permitAll() {
        return true;
    }

    public final boolean denyAll() {
        return false;
    }

    public final boolean isAnonymous() {
        return this.trustResolver.isAnonymous(this.authentication);
    }

    public final boolean isAuthenticated() {
        return !this.isAnonymous();
    }

    public final boolean isRememberMe() {
        return this.trustResolver.isRememberMe(this.authentication);
    }

    public final boolean isFullyAuthenticated() {
        return !this.trustResolver.isAnonymous(this.authentication) && !this.trustResolver.isRememberMe(this.authentication);
    }

    public Object getPrincipal() {
        return this.authentication.getPrincipal();
    }

    public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
        this.trustResolver = trustResolver;
    }

    public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
        this.roleHierarchy = roleHierarchy;
    }

    public void setDefaultRolePrefix(String defaultRolePrefix) {
        this.defaultRolePrefix = defaultRolePrefix;
    }

    private Set<String> getAuthoritySet() {
        if (this.roles == null) {
            this.roles = new HashSet();
            Collection<? extends GrantedAuthority> userAuthorities = this.authentication.getAuthorities();
            if (this.roleHierarchy != null) {
                userAuthorities = this.roleHierarchy.getReachableGrantedAuthorities(userAuthorities);
            }

            this.roles = AuthorityUtils.authorityListToSet(userAuthorities);
        }

        return this.roles;
    }

    public boolean hasPermission(Object target, Object permission) {
        return this.permissionEvaluator.hasPermission(this.authentication, target, permission);
    }

    public boolean hasPermission(Object targetId, String targetType, Object permission) {
        return this.permissionEvaluator.hasPermission(this.authentication, (Serializable)targetId, targetType, permission);
    }

    public void setPermissionEvaluator(PermissionEvaluator permissionEvaluator) {
        this.permissionEvaluator = permissionEvaluator;
    }

    private static String getRoleWithDefaultPrefix(String defaultRolePrefix, String role) {
        if (role == null) {
            return role;
        } else if (defaultRolePrefix != null && defaultRolePrefix.length() != 0) {
            return role.startsWith(defaultRolePrefix) ? role : defaultRolePrefix + role;
        } else {
            return role;
        }
    }
}

角色默認(rèn)前綴是ROLE_
hasAuthority([auth]):等同于hasRole
hasAnyAuthority([auth1,auth2]):等同于hasAnyRole
hasRole([role]):當(dāng)前用戶是否擁有指定角色桃序。
hasAnyRole([role1,role2]):多個(gè)角色是一個(gè)以逗號(hào)進(jìn)行分隔的字符串杖虾。如果當(dāng)前用戶擁有指定角色中的任意一個(gè)則返回true
Principle:代表當(dāng)前用戶的principle對(duì)象
authentication:直接從SecurityContext獲取的當(dāng)前Authentication對(duì)象
permitAll():總是返回true,表示允許所有的
denyAll():總是返回false媒熊,表示拒絕所有的
isAnonymous():當(dāng)前用戶是否是一個(gè)匿名用戶
isAuthenticated():表示當(dāng)前用戶是否已經(jīng)登錄認(rèn)證成功了
isRememberMe():表示當(dāng)前用戶是否是通過(guò)Remember-Me自動(dòng)登錄的
isFullyAuthenticated():如果當(dāng)前用戶既不是一個(gè)匿名用戶奇适,同時(shí)又不是通過(guò)Remember-Me自動(dòng)登錄的,則返回true
hasPermission():當(dāng)前用戶是否擁有指定權(quán)限

更多關(guān)于Spring-Security基于表達(dá)式的權(quán)限控制可以參考:
https://my.oschina.net/liuyuantao/blog/1924776

訪問(wèn)http://localhost:8080/roleAuth接口時(shí)只有ADMIN角色可以訪問(wèn)芦鳍,其他角色訪問(wèn)會(huì)報(bào)Forbidden403

image.png

注:Spring-Security嚴(yán)格區(qū)分大小寫

總結(jié):

Spring-Security的角色權(quán)限驗(yàn)證主要就是用到hasRole()hasPermission()

前后端分離下嚷往,需要在接口上面做認(rèn)證
具體實(shí)現(xiàn)參考:https://blog.csdn.net/cloume/article/details/83790111
前后端不分離的話,需要在前端展示頁(yè)面上做下角色權(quán)限校驗(yàn)設(shè)置柠衅,原理都一樣
具體實(shí)現(xiàn)參考:http://www.reibang.com/p/155ec4272aa4

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末皮仁,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子菲宴,更是在濱河造成了極大的恐慌魂贬,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件裙顽,死亡現(xiàn)場(chǎng)離奇詭異付燥,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)愈犹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門键科,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人漩怎,你說(shuō)我怎么就攤上這事勋颖。” “怎么了勋锤?”我有些...
    開封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵饭玲,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我叁执,道長(zhǎng)茄厘,這世上最難降的妖魔是什么矮冬? 我笑而不...
    開封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮次哈,結(jié)果婚禮上胎署,老公的妹妹穿的比我還像新娘。我一直安慰自己窑滞,他們只是感情好琼牧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著哀卫,像睡著了一般巨坊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上此改,一...
    開封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天抱究,我揣著相機(jī)與錄音,去河邊找鬼带斑。 笑死鼓寺,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的勋磕。 我是一名探鬼主播妈候,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼挂滓!你這毒婦竟也來(lái)了苦银?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤赶站,失蹤者是張志新(化名)和其女友劉穎幔虏,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贝椿,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡想括,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了烙博。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瑟蜈。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖渣窜,靈堂內(nèi)的尸體忽然破棺而出铺根,到底是詐尸還是另有隱情,我是刑警寧澤乔宿,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布位迂,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏掂林。R本人自食惡果不足惜臣缀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望党饮。 院中可真熱鬧,春花似錦驳庭、人聲如沸刑顺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蹲堂。三九已至,卻和暖如春贝淤,著一層夾襖步出監(jiān)牢的瞬間柒竞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工播聪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留朽基,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓离陶,卻偏偏與公主長(zhǎng)得像稼虎,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子招刨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

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