1. 程式人生 > 其它 >Spring Security 安全框架

Spring Security 安全框架

目錄

一、Spring Security 安全框架

1.介紹

Spring Security框架為我們提供了使用者認證和授權的能力。

  • 使用者認證: 登陸驗證
  • 使用者授權:某個使用者是否具有某個許可權。具有相應許可權的使用者才能進行具體的操作。比如: 管理員能進行工資設定,但是普通使用者只能檢視工資。

2.初體驗

1) 引入依賴

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

2)設計介面

在訪問介面時就會自動的被security的登陸頁面攔截

使用者名稱: user

密碼: 控制檯會提示的

3.過濾器鏈

security實際上就是一串過濾器鏈。通過過濾器鏈來實現使用者驗證和授權。

4.多種方式設定使用者名稱和密碼

方式一:yml配置檔案

# 設定使用者名稱和密碼
spring:
  security:
    user:
      name: qfadmin
      password: 123456

方式二: 設定配置類的方式

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

        String password = passwordEncoder.encode("123456");

        auth.inMemoryAuthentication().withUser("xiaoming").password(password).roles();

    }

    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

方式三:設定自定義登陸頁面

步驟:

  • 建立了一個UserDetailsService介面的實現類
/**
 * 建立一個UserDetailsService實現類來設定使用者名稱和密碼
 */
@Service("userDetailsService")
public class MyUserService implements UserDetailsService {
    
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        //設定角色,角色的概念在之後章節介紹
        List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
        //模擬從資料庫獲取使用者名稱和密碼
        User qfadmin = new User("qfadmin", passwordEncoder.encode("123456"), auths);

        return qfadmin;
    }
}

  • 編寫配置類
@Configuration
public class SecurityConfigPro extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .loginPage("/login.html") //設定自定義登陸頁面
                .loginProcessingUrl("/usr/login") //登陸時訪問的路徑
                .defaultSuccessUrl("/index").permitAll() //登陸成功後跳轉的路徑
                .and().authorizeRequests()
                .antMatchers("/","/hello","/user/login").permitAll() //設定可以直接訪問的路徑,取消攔截
                .anyRequest().authenticated()
                .and().csrf().disable(); //關閉csrf防護
    }
    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

  • 設計自定義登陸頁面

注意: 表單中的使用者名稱和密碼的name屬性必須是: username、password

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- 上述3個meta標籤*必須*放在最前面,任何其他內容都*必須*跟隨其後! -->
    <title>Bootstrap 101 Template</title>

    <!-- Bootstrap -->
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">

    <!-- HTML5 shim 和 Respond.js 是為了讓 IE8 支援 HTML5 元素和媒體查詢(media queries)功能 -->
    <!-- 警告:通過 file:// 協議(就是直接將 html 頁面拖拽到瀏覽器中)訪問頁面時 Respond.js 不起作用 -->
    <!--[if lt IE 9]>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/html5shiv.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dest/respond.min.js"></script>
    <![endif]-->
</head>
<body>

<form class="form-horizontal" action="/usr/login" method="post">
    <div class="form-group">
        <label for="inputEmail3" class="col-sm-2 control-label">Email</label>
        <div class="col-sm-10">
            <input type="text" name="username" class="form-control" id="inputEmail3" placeholder="Email">
        </div>
    </div>
    <div class="form-group">
        <label for="inputPassword3" class="col-sm-2 control-label">Password</label>
        <div class="col-sm-10">
            <input type="password" name="password" class="form-control" id="inputPassword3" placeholder="Password">
        </div>
    </div>
    <div class="form-group">
        <div class="col-sm-offset-2 col-sm-10">
            <div class="checkbox">
                <label>
                    <input type="checkbox"> Remember me
                </label>
            </div>
        </div>
    </div>
    <div class="form-group">
        <div class="col-sm-offset-2 col-sm-10">
            <button type="submit" class="btn btn-default">Sign in</button>
        </div>
    </div>
</form>

<!-- jQuery (Bootstrap 的所有 JavaScript 外掛都依賴 jQuery,所以必須放在前邊) -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js"></script>
<!-- 載入 Bootstrap 的所有 JavaScript 外掛。你也可以根據需要只加載單個外掛。 -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js"></script>
</body>
</html>

5.角色和許可權的四個方法

1)角色和許可權的概念

某個角色,擁有怎樣的許可權。

比如說管理員角色,擁有檔案管理、日誌管理的許可權

比如說 普通使用者角色,擁有檔案檢視的許可權。

對於角色和許可權來說,必須得看使用者屬於哪個角色,於是具有相應的許可權。

比如小明屬於普通使用者,那麼小明只有檢視檔案的許可權。

2)hasAuthority、hasAnyAuthority、hasRole、hasAnyRole

判斷當前登陸成功的使用者,能否具有訪問指定路徑的許可權的四個方法:

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .loginPage("/login.html") //設定自定義登陸頁面
                .loginProcessingUrl("/usr/login") //登陸時訪問的路徑
                .defaultSuccessUrl("/index").permitAll() //登陸成功後跳轉的路徑
                .and().authorizeRequests()
                .antMatchers("/","/hello","/usr/login").permitAll() //設定可以直接訪問的路徑,取消攔截
            
            
                //1.hasAuthority方法:當前登陸使用者,只有具有admin許可權才可以訪問這個路徑
//                .antMatchers("/index").hasAuthority("admins")
                //2.hasAnyAuthority方法:當前登陸使用者,具有指定的多個許可權中的某一個許可權即可訪問
                //.antMatchers("/index").hasAnyAuthority("admins","admin")
                //3.hasRole:當前登陸使用者是否屬於指定的角色
//                .antMatchers("/index").hasRole("student")
                //4.hasAnyRole: 當前登陸使用者滿足某一個角色即可
                .antMatchers("/index").hasAnyRole("student","admin")
                .anyRequest().authenticated()
                .and().csrf().disable(); //關閉csrf防護
    }

還需要修改,UserDetailsService中的auths集合,例子中直接寫死了許可權和角色,實際可以從資料庫獲取並返回。

 @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        //設定角色,角色的概念在之後章節介紹
        List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role,admin,ROLE_student");
        //模擬從資料庫獲取使用者名稱和密碼,且當前使用者有admin的許可權
        User qfadmin = new User("qfadmin", passwordEncoder.encode("123456"), auths);

        return qfadmin;
    }

6.security中的常用註解

  • Secured: 具有指定的角色許可權才能訪問註解的方法
 	@RequestMapping("/items")
    @Secured({"ROLE_student1"})
    @ResponseBody
    public String items(){
        return "items";
    }

  • PreAuthorize: 在訪問方法之前先進行角色和許可權的驗證
 @RequestMapping("/items")
    @PreAuthorize("hasAnyAuthority('admin')")
    @ResponseBody
    public String items(){
        return "items";
    }

  • PostAuthorize: 先執行方法,再進行角色和許可權的驗證
@RequestMapping("/postItems")
    @PostAuthorize("hasAnyAuthority('teacher')")
    @ResponseBody
    public String postItems(){
        //先執行方法內容,再做許可權校驗
        System.out.println("show detail here...");
        return "show post items";
    }


  • PostFilter:返回到前端的資料會被過濾,過濾條件在註解中宣告
    @RequestMapping("/users")
    @PreAuthorize("hasAnyAuthority('admin')")
    @PostFilter("filterObject.name=='xiaoming'")
    @ResponseBody
    public List<User> users(){

        List<User> users = new ArrayList<>();
        users.add(new User(1001L,"xiaoming"));
        users.add(new User(1002L,"xiaowang"));


        return users;
    }

  • PreFilter:只有符合條件的資料才會被傳入方法,條件在註解中宣告
	@RequestMapping("/preFilterItems")
    @PreAuthorize("hasAnyAuthority('admin')")
    @PreFilter(value="filterObject.userName == 'xiaoming'")
    public List<User> getUsersByPreFilter(@RequestBody List<User> list){
        //只有userName是'xiaoming'的資料才會被傳入
        list.forEach(t->{
            System.out.println(t.getUserName());
        });
        return list;
    }

關於如何校驗資料庫儲存的密碼。

 	@Test
    public void testPassword(){
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        //1.資料庫中要存密碼的密文

        String password = "abc123";//明文
        String encodePassword = passwordEncoder.encode(password);


        //2.使用者輸入一個明文,怎麼和資料庫的密文進行比較
        boolean matches = passwordEncoder.matches(password, encodePassword);
        System.out.println(matches);

    }

二、實現登出功能

1.登出的流程

2.步驟

1)在配置類中添加註銷配置

@Override
    protected void configure(HttpSecurity http) throws Exception {
        //配置沒有許可權的跳轉頁面
        http.exceptionHandling().accessDeniedPage("/error.html");

        //登出的配置
        http.logout().logoutUrl("/logout") //登出時訪問的路徑
                .logoutSuccessUrl("/logoutSuccess").permitAll(); //登出成功後訪問的路徑

        http.formLogin()
                .loginPage("/login.html") //設定自定義登陸頁面
                .loginProcessingUrl("/usr/login") //登陸時訪問的路徑
                .defaultSuccessUrl("/index").permitAll() //登陸成功後跳轉的路徑
                .and().authorizeRequests()
                .antMatchers("/","/hello","/user/login").permitAll() //設定可以直接訪問的路徑,取消攔截
                //1.hasAuthority方法:當前登陸使用者,只有具有admin許可權才可以訪問這個路徑
//                .antMatchers("/index").hasAuthority("admins")
                //2.hasAnyAuthority方法:當前登陸使用者,具有指定的多個許可權中的某一個許可權即可訪問
                //.antMatchers("/index").hasAnyAuthority("admins","admin")
                //3.hasRole:當前登陸使用者是否屬於指定的角色
//                .antMatchers("/index").hasRole("student")
                //4.hasAnyRole: 當前登陸使用者滿足某一個角色即可
//                .antMatchers("/index").hasAnyRole("student","admin")
                .anyRequest().authenticated()
                .and().csrf().disable(); //關閉csrf防護
    }

2)建立登出連結,連結中訪問登出路徑/logout

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"> 
    <title>首頁</title>
</head>
<body>
   歡迎來到 千鋒杭州Java2007大家庭!<a href="/logout">登出</a>
</body>
</html>

3)編寫登出成功後的跳轉介面

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