1. 程式人生 > >SpringBoot + Security學習筆記

SpringBoot + Security學習筆記

# SpringSecurity學習筆記 `本以為是總結,最後寫成了筆記,所以還是需要更加的努力啊。` > 開始的時候看了一遍官方文件,然後只看懂了加密器。 > > 然後又學了一個尚矽谷的視訊,雖然這個教程是在講一個專案,但我沒有聽懂(應該是我自己的問題) **程式碼** https://gitee.com/pilearn/learning-spring-security **中文版文件** https://www.springcloud.cc/spring-security.html **尚矽谷視訊連結** https://www.bilibili.com/video/BV15a411A7kP ### 什麼是SpringSecurity Security是Spring全家桶中一個安全框架,他的擴充套件能力非常的強,底層是一條過濾器鏈。通過簡單的配置就可以使用,但通過自己的DIY,可以把每個許可權細化到每個連結上去。 `shiro沒有學,但只推薦學一個安全框架` 這裡搭建的學習專案都是使用SpringBoot ### 獲取SpringSecurity 你可以在maven官網獲取最新版本
```xml org.springframework.boot spring-boot-starter-security 2.4.2 ``` ### 開始一個SpringBoot專案 ```xml 4.0.0 org.springframework.boot spring-boot-starter-parent 2.4.2 com.pipihao securitylearn 0.0.1-SNAPSHOT securitylearn Demo project for Spring Boot
1.8 com.alibaba druid 1.1.21 org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot-starter-security org.springframework.boot
spring-boot-starter-web
org.mybatis.spring.boot mybatis-spring-boot-starter 2.1.4 mysql mysql-connector-java runtime org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test org.springframework.security spring-security-test test
org.springframework.boot spring-boot-maven-plugin org.projectlombok lombok
``` ### 專案配置檔案 ```yml server: port: 8001 spring: datasource: url: jdbc:mysql://localhost:3306/demo?serverTimezone=Asia/Shanghai username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver thymeleaf: cache: false # 因為Thymeleaf很多有預設配置,所以只關了這個快取,方便重新整理 ``` ### 資料庫檔案 資料庫版本為 8.0 ```mysql ``` ### 執行專案 #### 登入 使用者名稱:user 密碼:控制檯輸出的這密碼 ### 配置Security #### 方法一:通過配置檔案修改登入賬號密碼 ```yml spring: security: user: name: xx password: xx ``` #### 方法二:通過自定義配置SecurityConfig配置類 WebSecurityConfigurerAdapter 類是是Security內建提供了一個預設身份驗證的抽象類,繼承此抽象類實現configure方法則可以對驗證操作實現DIY。[於官方文件 6.3 標題可見] UserDetailsService介面:查詢資料庫使用者名稱和密碼過程 * 建立類繼承UsernamePasswordAuthenticationFilter,重寫三個方法 * * 建立類實現UserDetailService,編寫查詢資料過程,返回User物件,這個User物件是安全框架提供物件。 * PasswordEncoder: 資料加密介面,用於返回User物件裡面的密碼加密 #### 方法三:自定義配置類UserDetailsService ### 定義不驗證連結 ```java @Override protected void configure(HttpSecurity http) throws Exception { /* 使用and()方法表示關閉XML標記的Java配置,它允許我們繼續配置父標記。如果您閱讀程式碼,它也是有道理的。我想配置授權請求並配置表單登入並配置HTTP基本身份驗證。 */ http .authorizeRequests() .antMatchers("/","/no").permitAll() //可以直接訪問的路徑 .anyRequest().authenticated() .and() .formLogin() .loginPage("/login.html") //配置登入路徑 .loginProcessingUrl("/doLogin") .defaultSuccessUrl("/hallo") .permitAll() ; //設定 登入的網頁 http.csrf().disable(); //如果註釋了這一行,全部要用_csrf的物件來驗證了 } ``` ### 配置訪問許可權/角色 如果是配置訪問角色則使用是hasRole與hasAnyRole **這裡非常建議點一下看一下hasRole的原始碼** 使用Role的時候,User的許可權列表是需要加ROLE_字首的 這裡直接使用的是hasAnyAuthority,還有一個方法是hasAuthority 前者可以配置多個許可權,而後者只能配置一個許可權 > 介面只是顯示一個字串 > > ```java > @GetMapping("test") > public String sayTest(){ > return "Test"; > } > ``` #### SecurityConfig程式碼 ```java @Override protected void configure(HttpSecurity http) throws Exception { /* 使用and()方法表示關閉XML標記的Java配置,它允許我們繼續配置父標記。如果您閱讀程式碼,它也是有道理的。我想配置授權請求並配置表單登入並配置HTTP基本身份驗證。 */ http .authorizeRequests() .antMatchers("/","/no").permitAll() //可以直接訪問的路徑 .antMatchers("/test").hasAnyAuthority("admin") // 訪問許可權 .anyRequest().authenticated() .and() .formLogin() .loginPage("/login.html") //配置登入路徑 .loginProcessingUrl("/doLogin") .defaultSuccessUrl("/hallo") .permitAll() ; //設定 登入的網頁 http.csrf().disable(); //如果註釋了這一行,全部要用_csrf的物件來驗證了 } ``` #### UserDetailsImpl程式碼 ```java @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { if(StringUtils.isEmpty(username)){ throw new RuntimeException("使用者名稱不能為空"); } IUser iUser= userMapper.getUserByUsername(username); if(iUser == null){ throw new UsernameNotFoundException("無此使用者"); } /*此處查詢使用者角色*/ List grantedAuthorityList = AuthorityUtils.createAuthorityList("admin"); // 許可權的列表 return new User(iUser.getUsername(),bCryptPasswordEncoder.encode(iUser.getPassword()),grantedAuthorityList); } ``` ### 自定義403介面 ``` // 在此方法內加上一行 protected void configure(HttpSecurity http) http.exceptionHandling().accessDeniedPage("/unauth.html"); ``` ### 許可權註解 #### @Secured > 判斷是否有角色,這裡匹配的角色需要加字首ROLE_ ```java @GetMapping("update") @Secured({"ROLE_manager"}) public String update(){ return "update"; } ``` 使用其功能時需要在application類上開起 ```java @SpringBootApplication @MapperScan("com.pipihao.securitylearn.mapper") @EnableGlobalMethodSecurity(securedEnabled = true) public class SecuritylearnApplication { public static void main(String[] args) { SpringApplication.run(SecuritylearnApplication.class, args); } } ``` UserDetailsServiceImpl ```java List grantedAuthorityList = AuthorityUtils.createAuthorityList("admin","ROLE_manager"); ``` #### @PreAuthorize & @PostAuthorize 此註解即有許可權驗證功能,又有角色驗證功能 ```java @GetMapping("pre1") @PreAuthorize("hasAnyRole('ROLE_manager')") public String prePost1(){ return "prePost1"; } @GetMapping("pre2") @PreAuthorize("hasAnyAuthority('admin')") public String prePost2(){ return "prePost2"; } ``` ```java @SpringBootApplication @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecuritylearnApplication { public static void main(String[] args) { SpringApplication.run(SecuritylearnApplication.class, args); } } ``` > @PostAuthorize 與@PreAuthorize的區別就是,Pre會先攔截後執行,而PostAuthorize是先執行,後攔截 > > 所以我例子中沒有過多的講 #### @PreFilter & @PostFilter Pre是過濾上傳的資料,Post過濾返回的資料 ```java @GetMapping("list") @PostFilter("filterObject.username != 'admin' ") public List list(){ List iUsers = new ArrayList<>(); iUsers.add(new IUser(1,"admin","123")); iUsers.add(new IUser(2,"user","123")); return iUsers; } // Applicationo類上還是要加上下面這個註解,並設定屬性值 @EnableGlobalMethodSecurity(prePostEnabled = true) ``` 效果圖 ![image-20210219115204162](https://gitee.com/pimg/image/raw/master/img/image-20210219115204162.png) 上傳則是同理,通過註解寫好判斷,然後測試即可,注:**PreFilter過濾的也只是集合和陣列** ### 使用者登出 ```java /*配置退出登入*/ http.logout().logoutUrl("/logout").logoutSuccessUrl("no").permitAll(); ``` 登入後,直接通過瀏覽器,訪問此路徑即可(是的,就是如此) ```js location.href='/logout'; ``` ### 自動登入 下面是尚矽谷老師寫的原理圖和執行流程 **如果是微服務,則把資料庫改成redis,把cookie改成jwt生成的token** Security 中的一個類內`JdbcTokenRepositoryImpl` 的常量CREATE_TABLE_SQL ```mysql create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, token varchar(64) not null, last_used timestamp not null) ``` **有興趣的可以看看原始碼** 沒興趣的直接在你使用的資料庫內執行上面這行sql建立一個儲存登入資訊的表 ![image-20210219185336468](https://gitee.com/pimg/image/raw/master/img/image-20210219185336468.png) JdbcTokenRepositoryImpl 是PersistentTokenRepository實現類 下面這種寫那麼應該是多型了 ```java @Autowired private DataSource dataSource; @Bean public PersistentTokenRepository persistentTokenRepository(){ JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl(); jdbcTokenRepository.setDataSource(dataSource); //jdbcTokenRepository.setCreateTableOnStartup(true); 設定啟動時建立自動登入表 return jdbcTokenRepository; } ``` **SecurityConfig的方法** ```java @Override protected void configure(HttpSecurity http) throws Exception { /*自定義403連結*/ http.exceptionHandling().accessDeniedPage("/unauth.html"); /*配置退出登入*/ http.logout().logoutUrl("/logout").logoutSuccessUrl("/no").permitAll(); /* 使用and()方法表示關閉XML標記的Java配置,它允許我們繼續配置父標記。如果您閱讀程式碼,它也是有道理的。我想配置授權請求並配置表單登入並配置HTTP基本身份驗證。 */ http .authorizeRequests() .antMatchers("/","/no").permitAll() //可以直接訪問的路徑 .antMatchers("/test").hasAnyAuthority("admin") .antMatchers("/unauth").hasAnyAuthority("xxx") .anyRequest().authenticated() .and() .formLogin() .loginPage("/login.html") //配置登入路徑 .loginProcessingUrl("/doLogin") .defaultSuccessUrl("/hallo") .permitAll() // -------------------就是下面這坨 .and() .rememberMe().tokenRepository(persistentTokenRepository()) .tokenValiditySeconds(60) // 自動儲存的時間,秒為單位 .userDetailsService(userDetailsService) ; //設定 登入的網頁 http.csrf().disable(); //如果註釋了這一行,全部要用_csrf的物件來驗證了 } ``` 下面是登入介面 ```html user:
pswd:
自動登入
``` 然後在登入的時候打個勾,就可以自動登入了 在DB中會出現如下的資訊 ![image-20210219191716331](https://gitee.com/pimg/image/raw/master/img/image-20210219191716331.png) ### CSRF指令認證 **第一步** 把下面這一行註釋了就開啟了,也就是說他其實是預設開啟的 如果沒有關閉,則會NullPointerException ```java //http.csrf().disable(); ``` Spring Security CSRF 會針對Patch,Post,Put,Delete方法進行防護。(都是一些要更改資料的方法) 系統預設提供了一個csrfToken物件放在HttpSession中,也就是我們所見到了_csrf物件 此物件可以直接使用 開啟CSRF後,則登入的時【POST】,也需要驗證CSRF,而使用HttpSession則需要使用模板引擎,這裡我們使用的是Thymeleaf而非JSP。(大同小異) > 注:使用Thymeleaf的時候,類上的Controller註解不能寫成RestController,不然無法生效的 ```java @Controller public class LoginController { @GetMapping("login") public String login(){ return "login"; } } ``` ```html 登入 user:
pswd:
自動登入
``` 切記,預設開了CSRF,則每個表單中應當手動新增一個隱藏域 當Thymeleaf因為你使用了th,則自動給你生成了。 所以 ` th:action="'/doLogin'"` 這樣寫可以省事 如下圖 ![image-20210219193359084](https://gitee.com/pimg/image/raw/master/img/image-20210219193359084.png) ## 總結 本是總結,誰知還是變成了學習筆記。總結代表著會,筆記代表著只能用,說不出什麼名堂。這是看第二遍,當然,這也會像我用正則一樣,每次用正則的時候,都要學一遍正則。 或許SpringSecurity並不難,難的只是步驟有點多。 老師講的很不錯,多聽幾遍就會了。 關於提高技術,應該看文件,把他提供的API都自己看懂。像用Redist代替DB,這樣的微服務中,使用,很有效率。 接下來,我還會繼續學習Security,並出些新筆記,這最多算是一個聽課