分布式--Spring Security入門

Spring Security是Spring推出的一個(gè)安全框架碟渺,說(shuō)白了就是爭(zhēng)對(duì)用戶登錄和權(quán)限的框架,所以主要功能為兩塊:“認(rèn)證”和“授權(quán)”扫尖,對(duì)應(yīng)用戶登錄和是否有權(quán)限去訪問(wèn)一些功能

一、使用Spring Security

1. 依賴

SpringBoot項(xiàng)目中加入依賴:

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

2. 寫(xiě)一個(gè)測(cè)試接口

@RestController
public class DemoController {

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

}

訪問(wèn):http://localhost:8080/demo 后,跳轉(zhuǎn)的為:

默認(rèn)賬號(hào)為:user衬浑,密碼在啟動(dòng)時(shí)的控制臺(tái)輸出:

輸入賬號(hào)密碼后登錄,就可以成功的訪問(wèn)接口了:

二放刨、自定義登錄邏輯

實(shí)際登錄中工秩,用戶的賬號(hào)密碼肯定需要通過(guò)數(shù)據(jù)庫(kù)查詢匹配,官方默認(rèn)只提供了一個(gè)默認(rèn)賬號(hào)进统,那么如何自定義用戶的登錄邏輯呢助币?

1. UserDetailsService

UserDetailsService接口需要實(shí)現(xiàn)loadUserByUsername()方法,該方法返回一個(gè)UserDetails對(duì)象螟碎,該對(duì)象是一個(gè)接口眉菱,其方法對(duì)應(yīng)的解釋看下面的注解:

public interface UserDetails extends Serializable {
    // 權(quán)限列表
    Collection<? extends GrantedAuthority> getAuthorities();

    // 密碼
    String getPassword();

    String getUsername();

    // 賬號(hào)是否過(guò)期
    boolean isAccountNonExpired();

    // 賬號(hào)是否被鎖定
    boolean isAccountNonLocked();

    // 憑證是否過(guò)期
    boolean isCredentialsNonExpired();

    // 是否可以
    boolean isEnabled();
}

其實(shí)現(xiàn)類為User

實(shí)現(xiàn)UserDetailsService接口,并使用實(shí)現(xiàn)類User構(gòu)造UserDetails掉分,User構(gòu)造傳入三個(gè)參數(shù):用戶名俭缓、密碼克伊、權(quán)限列表,密碼需要通過(guò)PasswordEncoder將原密碼進(jìn)行編碼后傳入华坦,會(huì)自動(dòng)和前端傳入的密碼進(jìn)行匹配愿吹,權(quán)限暫時(shí)構(gòu)造空的即可:

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 模擬數(shù)據(jù)庫(kù)查詢操作
        if (!username.equals("aruba")) throw new UsernameNotFoundException("用戶未找到");
        // 查詢出的密碼
        String pwd = "123";

        // 密碼解析器
        String encodePassword = passwordEncoder.encode(pwd);
        User user = new User(username, encodePassword, AuthorityUtils.commaSeparatedStringToAuthorityList(""));

        return user;
    }
}

2. PasswordEncoder

密碼解析器PasswordEncoder接口的方法解釋:

public interface PasswordEncoder {

    // 進(jìn)行編碼
    String encode(CharSequence rawPassword);

    // 原密碼和編碼后的密碼是否匹配
    boolean matches(CharSequence rawPassword, String encodedPassword);

    // 編碼的密碼能夠再次進(jìn)行解析且達(dá)到更安全的結(jié)果則返回true
    default boolean upgradeEncoding(String encodedPassword) {
        return false;
    }
}

密碼解析器實(shí)現(xiàn)類有很多:

我們使用最常用的BCryptPasswordEncoder

@Configuration
public class PasswordEncoderConfig {

    /**
     * 提供PasswordEncoder
     *
     * @return
     */
    @Bean
    protected PasswordEncoder providerPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

重啟項(xiàng)目,進(jìn)行訪問(wèn)季春,此時(shí)我們可以使用自定義的賬號(hào)和密碼進(jìn)行登錄了

三洗搂、自定義登錄界面

上面自定義了登錄邏輯,現(xiàn)在來(lái)對(duì)登錄界面進(jìn)行配置

1. 依賴

導(dǎo)入模板引擎:

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

2. 頁(yè)面編寫(xiě)

SpringSecurity中默認(rèn)使用usernamepassword作為登錄的請(qǐng)求參數(shù)载弄,默認(rèn)登錄接口:/login耘拇,使用post請(qǐng)求

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/login" method="post">
    用戶名:<input type="text" value="" name="username"><br/>
    密碼: <input type="password" name="password"><br/>

    <input type="submit" value="登錄">
</form>
</body>
</html>

3. SecurityFilterChain

提供注入SecurityFilterChain的方法,該方法有一個(gè)入?yún)?code>HttpSecurity:

@Configuration
public class SecurityConfig {

    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.formLogin().loginPage("/showLogin")//登錄頁(yè)面處理單元
                .loginProcessingUrl("/login")//登錄時(shí)提交的請(qǐng)求
                .successForwardUrl("/main");//登錄成功處理單元

        http.authorizeRequests()
                .antMatchers("/showLogin").permitAll() //配置不需要被認(rèn)證的請(qǐng)求
                .anyRequest().authenticated();//其他請(qǐng)求都必須被認(rèn)證宇攻。必須登錄后才能訪問(wèn)惫叛。

        http.csrf().disable();

        return http.build();
    }
}

4. controller編寫(xiě)

@Controller
public class LoginController {

    @RequestMapping("/showLogin")
    public String showLogin() {
        return "login";
    }

    @RequestMapping("/main")
    @ResponseBody
    public String main() {
        return "main";
    }

}

訪問(wèn)效果:

5. handler

除了使用successForwardUrl方法指定成功轉(zhuǎn)發(fā)的目標(biāo)外,還可以通過(guò)handler做自己想要的處理逞刷,比如使用重定向嘉涌,此處SpringSecurity不會(huì)做授權(quán)控制:

        http.formLogin().loginPage("/showLogin")//登錄頁(yè)面處理單元
                .loginProcessingUrl("/login")//登錄時(shí)提交的請(qǐng)求
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                        response.sendRedirect("/main");
                    }
                });

此外還有失敗的處理:failureHandler(AuthenticationFailureHandler)方法,使用上是一樣的

四夸浅、URL匹配

1. antMatchers通配符

上面我們已經(jīng)使用過(guò)antMatchers()方法來(lái)指定哪些請(qǐng)求不需要授權(quán)仑最,它還支持通配符

通配符 描述
? 匹配一個(gè)字符
* 匹配0個(gè)或多個(gè)字符
** 匹配0個(gè)或多個(gè)目錄

如放行js目錄下的所有文件:

.antMatchers("/js/**").permitAll()

2. antMatchers指定請(qǐng)求方式

通過(guò)第一個(gè)參數(shù),使用HttpMethod指定請(qǐng)求方式:

.antMatchers(HttpMethod.GET,"/js/**").permitAll()

除了antMatchers()方法以外帆喇,還可以使用regexMatchers()方法警医,匹配規(guī)則為正則表達(dá)式

五、角色權(quán)限判斷

1. 設(shè)置請(qǐng)求的角色權(quán)限

Spring Security權(quán)限分為兩種:權(quán)限角色坯钦,一個(gè)用戶可以擁有多個(gè)角色预皇,而一個(gè)角色可以擁有不同的權(quán)限

下面為配置權(quán)限的方法婉刀,在URL匹配后調(diào)用

權(quán)限方法 描述
hasAuthority(String) 只有擁有傳入?yún)?shù):權(quán)限吟温,才允許訪問(wèn)
hasAnyAuthority(String ...) 擁有任意一個(gè)權(quán)限,都可以訪問(wèn)
hasRole(String) 只有具備傳入?yún)?shù):角色 突颊,才允許訪問(wèn)
hasAnyRole(String ...) 擁有任意一個(gè)角色鲁豪,都可以訪問(wèn)
hasIpAddress(String) 指定ip,才可以訪問(wèn)

示例:

.antMatchers("/register").hasAuthority("register")//只有有注冊(cè)權(quán)限
.antMatchers("/modify").hasAnyAuthority("modify","register")//任意一個(gè)權(quán)限
.antMatchers("/manage").hasRole("admin")//只有admin角色
.antMatchers("/show").hasAnyRole("admin","user")//任意一個(gè)角色

2. 分配用戶的角色權(quán)限

上面只是爭(zhēng)對(duì)不同的請(qǐng)求配置了權(quán)限和角色律秃,想要用戶擁有權(quán)限和角色呈昔,就需要在UserDetails中進(jìn)行添加,之前我們權(quán)限暫時(shí)設(shè)置為了空友绝。
權(quán)限和角色設(shè)置規(guī)則:多個(gè)權(quán)限和角色使用,分開(kāi),角色需要添加ROLE_前綴:

User user = new User(username, encodePassword, 
        AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_admin,modify"));

3. 拒絕權(quán)限處理

successHandler()一樣肝劲,Spring Security也可以自定義拒絕權(quán)限的處理迁客,使用accessDeniedHandler(AccessDeniedHandler)方法:

// 拒絕訪問(wèn)的處理
http.exceptionHandling().accessDeniedHandler(new AccessDeniedHandler() {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        response.setHeader("Content-Type", "application/json;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.write("{\"status\":\"error\",\"msg\":\"權(quán)限不足郭宝,請(qǐng)聯(lián)系管理員!\"}");
        out.flush();
        out.close();
    }
});

4. 權(quán)限自定義判斷

針對(duì)一些特殊的需求,我們可能要自定義權(quán)限的判斷邏輯掷漱,Spring Security也支持粘室,只要按照它提供的規(guī)則進(jìn)行代碼編寫(xiě)

4.1 boolean hasPermission(HttpServletRequest,Authentication)

需要定義一個(gè)接口,里面有該方法:

public interface MyAccessService {
    boolean hasPermission(HttpServletRequest request, Authentication authentication);
}

實(shí)現(xiàn)接口:

@Service
public class MyAccessServiceImpl implements MyAccessService {
    @Override
    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
        // 獲取對(duì)象
        Object principal = authentication.getPrincipal();
        if (principal instanceof UserDetails) {
            // 轉(zhuǎn)為UserDetails對(duì)象
            UserDetails userDetails = (UserDetails) principal;
            // 獲取所有權(quán)限
            Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
            // 做自己的處理
            return authorities.contains(new SimpleGrantedAuthority("ROLE_admin"));
        }
        return false;
    }
}
4.2 使用自定義

通過(guò)@+注入bean的方式調(diào)用方法:

.anyRequest().access("@myAccessServiceImpl.hasPermission(request,authentication)");

5. 注解設(shè)置請(qǐng)求權(quán)限

除了通過(guò)config方式外卜范,還可以通過(guò)注解來(lái)指定controller層哪個(gè)請(qǐng)求使用哪些權(quán)限衔统,需要在SpringBoot啟動(dòng)類上開(kāi)啟@EnableMethodSecurity注解:

支持的注解有:

注解 描述
@Secured 指定處理單元的權(quán)限和角色,參數(shù)為數(shù)組海雪,使用需要開(kāi)啟@EnableMethodSecurity注解的securedEnabled
@PreAuthorize 在處理單元之前進(jìn)行權(quán)限和角色的控制锦爵,使用權(quán)限表達(dá)式進(jìn)行授權(quán)
@PostAuthorize 在處理單元之后進(jìn)行權(quán)限和角色的控制

示例:

@RestController
public class DemoController {

    // Secured角色需要加上ROLE前綴
    @Secured({"ROLE_admin", "register"})
    @RequestMapping("/demo")
    public String demo() {
        return "demo";
    }

    // PreAuthorize中可以調(diào)用方法
    @PreAuthorize("hasRole('admin')")
    @RequestMapping("/demo2")
    public String demo2() {
        return "demo";
    }
}

六、記住登錄

Spring Security記住登錄功能依賴數(shù)據(jù)庫(kù)實(shí)現(xiàn)奥裸,需要進(jìn)行以下配置

1. 依賴

       <dependency>
           <groupId>org.mybatis.spring.boot</groupId>
           <artifactId>mybatis-spring-boot-starter</artifactId>
           <version>2.1.3</version>
       </dependency>
       <dependency>
           <groupId>mysql</groupId>
           <artifactId>mysql-connector-java</artifactId>
           <version>8.0.21</version>
       </dependency>

yml中進(jìn)行配置:

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

2. PersistentTokenRepository

提供PersistentTokenRepository险掀,使用它的實(shí)現(xiàn)類:JdbcTokenRepositoryImpl

@Configuration
public class RememberConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    protected PersistentTokenRepository providerTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        // 啟動(dòng)時(shí),自動(dòng)創(chuàng)建表湾宙,第二次啟動(dòng)注釋
        jdbcTokenRepository.setCreateTableOnStartup(true);
        jdbcTokenRepository.setDataSource(dataSource);

        return jdbcTokenRepository;
    }

}

3. 配置HttpSecurity

        // 持久處理
        http.rememberMe()
                .userDetailsService(userDetailsService) //登錄邏輯交給哪個(gè)對(duì)象
                .tokenValiditySeconds(5000) // 表示持久化時(shí)間
                .tokenRepository(repository);   //持久層對(duì)象

4. 頁(yè)面添加標(biāo)簽

添加nameremember-me的標(biāo)簽:

記住: <input type="checkbox" name="remember-me"><br/>

之后網(wǎng)頁(yè)只需要登錄一次樟氢,就可以持久5000s不需要登錄,即使服務(wù)重啟也可以保持登錄狀態(tài)

項(xiàng)目地址:

https://gitee.com/aruba/spring-security-study.git

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末侠鳄,一起剝皮案震驚了整個(gè)濱河市埠啃,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌伟恶,老刑警劉巖碴开,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異知押,居然都是意外死亡叹螟,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門台盯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)罢绽,“玉大人,你說(shuō)我怎么就攤上這事静盅×技郏” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵蒿叠,是天一觀的道長(zhǎng)明垢。 經(jīng)常有香客問(wèn)我,道長(zhǎng)市咽,這世上最難降的妖魔是什么痊银? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮施绎,結(jié)果婚禮上溯革,老公的妹妹穿的比我還像新娘贞绳。我一直安慰自己,他們只是感情好致稀,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布冈闭。 她就那樣靜靜地躺著,像睡著了一般抖单。 火紅的嫁衣襯著肌膚如雪萎攒。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 48,970評(píng)論 1 284
  • 那天矛绘,我揣著相機(jī)與錄音耍休,去河邊找鬼。 笑死蔑歌,一個(gè)胖子當(dāng)著我的面吹牛羹应,可吹牛的內(nèi)容都是我干的降宅。 我是一名探鬼主播绊寻,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼戚丸!你這毒婦竟也來(lái)了劫灶?” 一聲冷哼從身側(cè)響起裸违,我...
    開(kāi)封第一講書(shū)人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎本昏,沒(méi)想到半個(gè)月后供汛,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡涌穆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年怔昨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宿稀。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡趁舀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出祝沸,到底是詐尸還是另有隱情矮烹,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布罩锐,位于F島的核電站奉狈,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏涩惑。R本人自食惡果不足惜仁期,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蟀拷,春花似錦碰纬、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)寿桨。三九已至此衅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間亭螟,已是汗流浹背挡鞍。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留预烙,地道東北人墨微。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像扁掸,于是被迫代替她去往敵國(guó)和親翘县。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

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

  • Spring Security 由于配置復(fù)雜谴分,一直被人所詬病锈麸,所以對(duì)于 SSM 框架的項(xiàng)目來(lái)說(shuō),輕量的 Shiro...
    Java弟中弟閱讀 712評(píng)論 0 1
  • 一牺蹄、搭建注冊(cè)中心 1.1 需求分析 回顧技術(shù)方案如下: 1忘伞、UAA認(rèn)證服務(wù)負(fù)責(zé)認(rèn)證授權(quán)。2沙兰、所有請(qǐng)求經(jīng)過(guò)網(wǎng)關(guān)到達(dá)微...
    Doooook閱讀 3,336評(píng)論 3 18
  • 一鼎天、Spring security框架簡(jiǎn)介 Spring Security是Spring社區(qū)的一個(gè)頂級(jí)項(xiàng)目舀奶,也是S...
    zenghi閱讀 1,444評(píng)論 0 3
  • 一、簡(jiǎn)介 Spring Security是一個(gè)高度自定義的安全框架训措。利用Spring IoC/DI和AOP功能伪节,為...
    文景大大閱讀 1,288評(píng)論 0 3
  • security 介紹 Spring Security是能夠?yàn)镴2EE項(xiàng)目提供綜合性的安全訪問(wèn)控制解決方案的安全框...
    ikonan閱讀 622評(píng)論 1 0