<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          ?Spring Boot + Spring Security + Thymeleaf 實戰(zhàn)教程!

          共 22066字,需瀏覽 45分鐘

           ·

          2021-06-03 06:47

          Spring Security 基本原理

          Spring Security 過濾器鏈

          Spring Security實現(xiàn)了一系列的過濾器鏈,就按照下面順序一個一個執(zhí)行下去。

          1. ....class 一些自定義過濾器(在配置的時候你可以自己選擇插到哪個過濾器之前),因為這個需求因人而異,本文不探討,大家可以自己研究
          2. UsernamePasswordAithenticationFilter.class Spring Security 自帶的表單登入驗證過濾器,也是本文主要使用的過濾器
          3. BasicAuthenticationFilter.class
          4. ExceptionTranslation.class 異常解釋器
          5. FilterSecurityInterceptor.class 攔截器最終決定請求能否通過
          6. Controller 我們最后自己編寫的控制器

          相關(guān)類說明

          • User.class :注意這個類不是我們自己寫的,而是Spring Security官方提供的,他提供了一些基礎(chǔ)的功能,我們可以通過繼承這個類來擴充方法。詳見代碼中的 CustomUser.java
          • UserDetailsService.class:Spring Security官方提供的一個接口,里面只有一個方法loadUserByUsername() ,Spring Security會調(diào)用這個方法來獲取數(shù)據(jù)庫中存在的數(shù)據(jù),然后和用戶POST過來的用戶名密碼進行比對,從而判斷用戶的用戶名密碼是否正確。所以我們需要自己實現(xiàn)loadUserByUsername() 這個方法。詳見代碼中的 CustomUserDetailsService.java

          項目邏輯

          為了體現(xiàn)權(quán)限區(qū)別,我們通過HashMap構(gòu)造了一個數(shù)據(jù)庫,里面包含了4個用戶

          ID用戶名密碼權(quán)限
          1jackjack123user
          2dannydanny123editor
          3alicealice123reviewer
          4smithsmith123admin

          說明下權(quán)限

          user:最基礎(chǔ)的權(quán)限,只要是登入用戶就有 user 權(quán)限

          editor:在 user 權(quán)限上面增加了 editor 的權(quán)限

          reviewer:與上同理,editorreviewer 屬于同一級的權(quán)限

          admin:包含所有權(quán)限

          為了檢驗權(quán)限,我們提供若干個頁面

          網(wǎng)址說明可訪問權(quán)限
          /首頁所有人均可訪問(anonymous)
          /login登入頁面所有人均可訪問(anonymous)
          /logout退出頁面所有人均可訪問(anonymous)
          /user/home用戶中心user
          /user/editor
          editor, admin
          /user/reviewer
          reviewer, admin
          /user/admin
          admin
          /403403錯誤頁面,美化過,大家可以直接用所有人均可訪問(anonymous)
          /404404錯誤頁面,美化過,大家可以直接用所有人均可訪問(anonymous)
          /500500錯誤頁面,美化過,大家可以直接用所有人均可訪問(anonymous)

          代碼配置

          Maven 配置

          <?xml version="1.0" encoding="UTF-8"?>
          <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
              <modelVersion>4.0.0</modelVersion>
              <parent>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-parent</artifactId>
                  <version>2.1.1.RELEASE</version>
                  <relativePath/> <!-- lookup parent from repository -->
              </parent>
              <groupId>org.inlighting</groupId>
              <artifactId>security-demo</artifactId>
              <version>0.0.1-SNAPSHOT</version>
              <name>security-demo</name>
              <description>Demo project for Spring Boot &amp; Spring Security</description>

              <!--指定JDK版本,大家可以改成自己的-->
              <properties>
                  <java.version>11</java.version>
              </properties>

              <dependencies>
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-security</artifactId>
                  </dependency>
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-thymeleaf</artifactId>
                  </dependency>
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-web</artifactId>
                  </dependency>
                  <!--對Thymeleaf添加Spring Security標簽支持-->
                  <dependency>
                      <groupId>org.thymeleaf.extras</groupId>
                      <artifactId>thymeleaf-extras-springsecurity5</artifactId>
                  </dependency>
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-test</artifactId>
                      <scope>test</scope>
                  </dependency>
                  <dependency>
                      <groupId>org.springframework.security</groupId>
                      <artifactId>spring-security-test</artifactId>
                      <scope>test</scope>
                  </dependency>
                  <!--開發(fā)的熱加載配置-->
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-devtools</artifactId>
                      <optional>true</optional>
                  </dependency>
              </dependencies>

              <build>
                  <plugins>
                      <plugin>
                          <groupId>org.springframework.boot</groupId>
                          <artifactId>spring-boot-maven-plugin</artifactId>
                      </plugin>
                  </plugins>
              </build>
          </project>

          application.properties配置

          為了使熱加載(這樣修改模板后無需重啟 Tomcat )生效,我們需要在Spring Boot的配置文件上面加上一段話

          spring.thymeleaf.cache=false

          如果需要詳細了解熱加載,請看官方文檔:https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-hotswapping

          Spring Security 配置

          首先我們開啟方法注解支持:只需要在類上添加 @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) 注解,我們設(shè)置 prePostEnabled = true 是為了支持 hasRole() 這類表達式。如果想進一步了解方法注解可以看 Introduction to Spring Method Security 這篇文章。

          SecurityConfig.java

          /**
           * 開啟方法注解支持,我們設(shè)置prePostEnabled = true是為了后面能夠使用hasRole()這類表達式
           * 進一步了解可看教程:https://www.baeldung.com/spring-security-method-security
           */
          @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
          @Configuration
          public class SecurityConfig extends WebSecurityConfigurerAdapter {

              /**
               * TokenBasedRememberMeServices的生成密鑰,
               * 算法實現(xiàn)詳見文檔:https://docs.spring.io/spring-security/site/docs/5.1.3.RELEASE/reference/htmlsingle/#remember-me-hash-token
               */
              private final String SECRET_KEY = "123456";

              @Autowired
              private CustomUserDetailsService customUserDetailsService;

              /**
               * 必須有此方法,Spring Security官方規(guī)定必須要有一個密碼加密方式。
               * 注意:例如這里用了BCryptPasswordEncoder()的加密方法,那么在保存用戶密碼的時候也必須使用這種方法,確保前后一致。
               * 詳情參見項目中Database.java中保存用戶的邏輯
               */
              @Bean
              public PasswordEncoder passwordEncoder() {
                  return new BCryptPasswordEncoder();
              }

              /**
               * 配置Spring Security,下面說明幾點注意事項。
               * 1. Spring Security 默認是開啟了CSRF的,此時我們提交的POST表單必須有隱藏的字段來傳遞CSRF,
               * 而且在logout中,我們必須通過POST到 /logout 的方法來退出用戶,詳見我們的login.html和logout.html.
               * 2. 開啟了rememberMe()功能后,我們必須提供rememberMeServices,例如下面的getRememberMeServices()方法,
               * 而且我們只能在TokenBasedRememberMeServices中設(shè)置cookie名稱、過期時間等相關(guān)配置,如果在別的地方同時配置,會報錯。
               * 錯誤示例:xxxx.and().rememberMe().rememberMeServices(getRememberMeServices()).rememberMeCookieName("cookie-name")
               */
              @Override
              protected void configure(HttpSecurity http) throws Exception {
                  http.formLogin()
                          .loginPage("/login") // 自定義用戶登入頁面
                          .failureUrl("/login?error") // 自定義登入失敗頁面,前端可以通過url中是否有error來提供友好的用戶登入提示
                          .and()
                          .logout()
                          .logoutUrl("/logout")// 自定義用戶登出頁面
                          .logoutSuccessUrl("/")
                          .and()
                          .rememberMe() // 開啟記住密碼功能
                          .rememberMeServices(getRememberMeServices()) // 必須提供
                          .key(SECRET_KEY) // 此SECRET需要和生成TokenBasedRememberMeServices的密鑰相同
                          .and()
                          /*
                           * 默認允許所有路徑所有人都可以訪問,確保靜態(tài)資源的正常訪問。
                           * 后面再通過方法注解的方式來控制權(quán)限。
                           */
                          .authorizeRequests().anyRequest().permitAll()
                          .and()
                          .exceptionHandling().accessDeniedPage("/403"); // 權(quán)限不足自動跳轉(zhuǎn)403
              }

              /**
               * 如果要設(shè)置cookie過期時間或其他相關(guān)配置,請在下方自行配置
               */
              private TokenBasedRememberMeServices getRememberMeServices() {
                  TokenBasedRememberMeServices services = new TokenBasedRememberMeServices(SECRET_KEY, customUserDetailsService);
                  services.setCookieName("remember-cookie");
                  services.setTokenValiditySeconds(100); // 默認14天
                  return services;
              }
          }

          UserService.java

          自己模擬數(shù)據(jù)庫操作的Service,用于向自己通過HashMap模擬的數(shù)據(jù)源獲取數(shù)據(jù)。

          @Service
          public class UserService {

              private Database database = new Database();

              public CustomUser getUserByUsername(String username) {
                  CustomUser originUser = database.getDatabase().get(username);
                  if (originUser == null) {
                      return null;
                  }

                  /*
                   * 此處有坑,之所以這么做是因為Spring Security獲得到User后,會把User中的password字段置空,以確保安全。
                   * 因為Java類是引用傳遞,為防止Spring Security修改了我們的源頭數(shù)據(jù),所以我們復制一個對象提供給Spring Security。
                   * 如果通過真實數(shù)據(jù)庫的方式獲取,則沒有這種問題需要擔心。
                    */
                  return new CustomUser(originUser.getId(), originUser.getUsername(), originUser.getPassword(), originUser.getAuthorities());
              }
          }

          CustomUserDetailsService.java

          /**
           * 實現(xiàn)官方提供的UserDetailsService接口即可
           */
          @Service
          public class CustomUserDetailsService implements UserDetailsService {

              private Logger LOGGER = LoggerFactory.getLogger(getClass());

              @Autowired
              private UserService userService;

              @Override
              public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
                  CustomUser user = userService.getUserByUsername(username);
                  if (user == null) {
                      throw new  UsernameNotFoundException("該用戶不存在");
                  }
                  LOGGER.info("用戶名:"+username+" 角色:"+user.getAuthorities().toString());
                  return user;
              }
          }

          自定義權(quán)限注解

          我們在開發(fā)網(wǎng)站的過程中,比如 GET /user/editor這個請求角色為 EDITORADMIN 肯定都可以,如果我們在每一個需要判斷權(quán)限的方法上面寫一長串的權(quán)限表達式,一定很復雜。但是通過自定義權(quán)限注解,我們可以通過 @IsEditor 這樣的方法來判斷,這樣一來就簡單了很多。進一步了解可以看:Introduction to Spring Method Security

          IsUser.java

          @Target({ElementType.METHOD, ElementType.TYPE})
          @Retention(RetentionPolicy.RUNTIME)
          @PreAuthorize("hasAnyAuthority('ROLE_USER', 'ROLE_EDITOR', 'ROLE_REVIEWER', 'ROLE_ADMIN')")
          public @interface IsUser {
          }

          IsEditor.java

          @Target({ElementType.METHOD, ElementType.TYPE})
          @Retention(RetentionPolicy.RUNTIME)
          @PreAuthorize("hasAnyRole('ROLE_USER', 'ROLE_EDITOR', 'ROLE_ADMIN')")
          public @interface IsEditor {
          }

          IsReviewer.java

          @Target({ElementType.METHOD, ElementType.TYPE})
          @Retention(RetentionPolicy.RUNTIME)
          @PreAuthorize("hasAnyRole('ROLE_USER', 'ROLE_REVIEWER', 'ROLE_ADMIN')")
          public @interface IsReviewer {
          }

          IsAdmin.java

          @Target({ElementType.METHOD, ElementType.TYPE})
          @Retention(RetentionPolicy.RUNTIME)
          @PreAuthorize("hasAnyRole('ROLE_ADMIN')")
          public @interface IsAdmin { 
          }

          Spring Security自帶表達式

          • hasRole(),是否擁有某一個權(quán)限
          • hasAnyRole(),多個權(quán)限中有一個即可,如 hasAnyRole("ADMIN","USER")
          • hasAuthority()AuthorityRole 很像,唯一的區(qū)別就是 Authority 前綴多了 ROLE_ ,如 hasAuthority("ROLE_ADMIN") 等價于 hasRole("ADMIN") ,可以參考上面 IsUser.java 的寫法
          • hasAnyAuthority(),同上,多個權(quán)限中有一個即可
          • permitAll(), denyAll(),isAnonymous(), isRememberMe(),通過字面意思可以理解
          • isAuthenticated(), isFullyAuthenticated(),這兩個區(qū)別就是isFullyAuthenticated()對認證的安全要求更高。例如用戶通過記住密碼功能登入到系統(tǒng)進行敏感操作,isFullyAuthenticated()會返回false,此時我們可以讓用戶再輸入一次密碼以確保安全,而 isAuthenticated() 只要是登入用戶均返回true
          • principal(), authentication(),例如我們想獲取登入用戶的id,可以通過principal() 返回的 Object 獲取,實際上 principal() 返回的 Object 基本上可以等同我們自己編寫的 CustomUser 。而 authentication() 返回的 AuthenticationPrincipal 的父類,相關(guān)操作可看 Authentication 的源碼。進一步了解可以看后面Controller編寫中獲取用戶數(shù)據(jù)的四種方法
          • hasPermission(),參考字面意思即可

          如果想進一步了解,可以參考 Intro to Spring Security Expressions。

          添加Thymeleaf支持

          我們通過 thymeleaf-extras-springsecurity 來添加Thymeleaf對Spring Security的支持。

          Maven配置

          上面的Maven配置已經(jīng)加過了

          <dependency>
              <groupId>org.thymeleaf.extras</groupId>
              <artifactId>thymeleaf-extras-springsecurity5</artifactId>
          </dependency>

          使用例子

          注意我們要在html中添加 xmlns:sec 的支持

          <!DOCTYPE html>
          <html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
          <head>
              <meta charset="UTF-8">
              <title>Admin</title>
          </head>
          <body>
          <p>This is a home page.</p>
          <p>Id: <th:block sec:authentication="principal.id"></th:block></p>
          <p>Username: <th:block sec:authentication="principal.username"></th:block></p>
          <p>Role: <th:block sec:authentication="principal.authorities"></th:block></p>
          </body>
          </html>

          如果想進一步了解請看文檔 thymeleaf-extras-springsecurity。

          Controller編寫

          IndexController.java

          本控制器沒有任何的權(quán)限規(guī)定

          @Controller
          public class IndexController {

              @GetMapping("/")
              public String index() {
                  return "index/index";
              }

              @GetMapping("/login")
              public String login() {
                  return "index/login";
              }

              @GetMapping("/logout")
              public String logout() {
                  return "index/logout";
              }
          }

          UserController.java

          在這個控制器中,我綜合展示了自定義注解的使用和4種獲取用戶信息的方式

          @IsUser // 表明該控制器下所有請求都需要登入后才能訪問
          @Controller
          @RequestMapping("/user")
          public class UserController {

              @GetMapping("/home")
              public String home(Model model) {
                  // 方法一:通過SecurityContextHolder獲取
                  CustomUser user = (CustomUser)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
                  model.addAttribute("user", user);
                  return "user/home";
              }

              @GetMapping("/editor")
              @IsEditor
              public String editor(Authentication authentication, Model model) {
                  // 方法二:通過方法注入的形式獲取Authentication
                  CustomUser user = (CustomUser)authentication.getPrincipal();
                  model.addAttribute("user", user);
                  return "user/editor";
              }

              @GetMapping("/reviewer")
              @IsReviewer
              public String reviewer(Principal principal, Model model) {
                  // 方法三:同樣通過方法注入的方法,注意要轉(zhuǎn)型,此方法很二,不推薦
                  CustomUser user = (CustomUser) ((Authentication)principal).getPrincipal();
                  model.addAttribute("user", user);
                  return "user/reviewer";
              }

              @GetMapping("/admin")
              @IsAdmin
              public String admin() {
                  // 方法四:通過Thymeleaf的Security標簽進行,詳情見admin.html
                  return "user/admin";
              }
          }

          注意

          • 如果有安全控制的方法 A 被同一個類中別的方法調(diào)用,那么方法 A 的權(quán)限控制會被忽略,私有方法同樣會受到影響
          • Spring 的 SecurityContext 是線程綁定的,如果我們在當前的線程中新建了別的線程,那么他們的 SecurityContext 是不共享的,進一步了解請看 Spring Security Context Propagation with @Async

          Html的編寫

          在編寫html的時候,基本上就是大同小異了,就是注意一點,**如果開啟了CSRF,在編寫表單POST請求的時候添加上隱藏字段,如 **<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/> ,不過大家其實不用加也沒事,因為Thymeleaf自動會加上去的??。

          github地址:https://github.com/Smith-Cruise/Spring-Boot-Security-Thymeleaf-Demo

          點擊閱讀全文前往微服務(wù)電商教程
          瀏覽 56
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  大香蕉色婷婷 | 最新中文字幕2019视频在线不卡 | 色永久免费视频 | 婷婷 大香蕉 伊人 五月 | 亚洲欧美日韩性爱 |