<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 Security 最佳實(shí)踐

          共 24676字,需瀏覽 50分鐘

           ·

          2022-10-15 14:36


          來源:juejin.cn/post/7026734817853210661

          • Spring Security簡介
          • Spring Security 認(rèn)證流程
          • Spring Security 項(xiàng)目搭建
          • 導(dǎo)入依賴
          • 訪問頁面
          • 自定義用戶名和密碼
          • UserDetailsService詳解
          • PasswordEncoder密碼解析器詳解
          • 登錄配置
          • 角色權(quán)限
          • 403 權(quán)限不足頁面處理
          • RememberMe(記住我)
          • Spring Security 注解
          • Spring Security中CSRF
          • 什么是CSRF?

          今天來一篇 Spring Security 精講,相信你看過之后能徹底搞懂 Spring Security。

          Spring Security簡介

          Spring Security 是一種高度自定義的安全框架,利用(基于)SpringIOC/DI和AOP功能,為系統(tǒng)提供了聲明式安全訪問控制功能,「減少了為系統(tǒng)安全而編寫大量重復(fù)代碼的工作」 。

          「核心功能:認(rèn)證和授權(quán)」

          Spring Security 認(rèn)證流程

          SpringSecurity認(rèn)證執(zhí)行流程

          Spring Security 項(xiàng)目搭建

          導(dǎo)入依賴

          Spring Security已經(jīng)被Spring boot進(jìn)行集成,使用時(shí)直接引入啟動器即可

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

          訪問頁面

          導(dǎo)入spring-boot-starter-security啟動器后,Spring Security已經(jīng)生效,默認(rèn)攔截全部請求,如果用戶沒有登錄,跳轉(zhuǎn)到內(nèi)置登錄頁面。

          在瀏覽器輸入:http://localhost:8080/ 進(jìn)入Spring Security內(nèi)置登錄頁面

          用戶名:user。

          密碼:項(xiàng)目啟動,打印在控制臺中。

          自定義用戶名和密碼

          修改「application.yml」 文件

          # 靜態(tài)用戶,一般只在內(nèi)部網(wǎng)絡(luò)認(rèn)證中使用,如:內(nèi)部服務(wù)器1,訪問服務(wù)器2
          spring:
            security:
              user:
                name: test  # 通過配置文件,設(shè)置靜態(tài)用戶名
                password: test # 配置文件,設(shè)置靜態(tài)登錄密碼

          UserDetailsService詳解

          什么也沒有配置的時(shí)候,賬號和密碼是由Spring Security定義生成的。而在實(shí)際項(xiàng)目中賬號和密碼都是從數(shù)據(jù)庫中查詢出來的。所以我們要通過「自定義邏輯控制認(rèn)證邏輯」 。如果需要自定義邏輯時(shí),只需要實(shí)現(xiàn)UserDetailsService接口

          @Component
          public class UserSecurity implements UserDetailsService {

              @Autowired
              private UserService userService;

              @Override
              public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {

                  User user = userService.login(userName);
                  System.out.println(user);
                  if (null==user){
                      throw new UsernameNotFoundException("用戶名錯誤");
                  }
                  org.springframework.security.core.userdetails.User result =
                          new org.springframework.security.core.userdetails.User(
                                  userName,user.getPassword(), AuthorityUtils.createAuthorityList()
                          );
                  return result;
              }

          }

          PasswordEncoder密碼解析器詳解

          PasswordEncoder

          「PasswordEncoder」 是SpringSecurity 的密碼解析器,用戶密碼校驗(yàn)、加密 。自定義登錄邏輯時(shí)要求必須給容器注入PaswordEncoder的bean對象

          SpringSecurity 定義了很多實(shí)現(xiàn)接口「PasswordEncoder」 滿足我們密碼加密、密碼校驗(yàn) 使用需求。

          PasswordEncoder密碼解析器詳解

          自定義密碼解析器

          1. 編寫類,實(shí)現(xiàn)PasswordEncoder 接口
          /**
           * 憑證匹配器,用于做認(rèn)證流程的憑證校驗(yàn)使用的類型
           * 其中有2個核心方法
           * 1. encode - 把明文密碼,加密成密文密碼
           * 2. matches - 校驗(yàn)明文和密文是否匹配
           * */
          public class MyMD5PasswordEncoder implements PasswordEncoder {

              /**
               * 加密
               * @param charSequence  明文字符串
               * @return
               */
              @Override
              public String encode(CharSequence charSequence) {
                  try {
                      MessageDigest digest = MessageDigest.getInstance("MD5");
                      return toHexString(digest.digest(charSequence.toString().getBytes()));
                  } catch (NoSuchAlgorithmException e) {
                      e.printStackTrace();
                      return "";
                  }
              }

              /**
               * 密碼校驗(yàn)
               * @param charSequence 明文,頁面收集密碼
               * @param s 密文 ,數(shù)據(jù)庫中存放密碼
               * @return
               */
              @Override
              public boolean matches(CharSequence charSequence, String s) {
                  return s.equals(encode(charSequence));
              }

               /**
               * @param tmp 轉(zhuǎn)16進(jìn)制字節(jié)數(shù)組
               * @return 飯回16進(jìn)制字符串
               */
              private String toHexString(byte [] tmp){
                  StringBuilder builder = new StringBuilder();
                  for (byte b :tmp){
                      String s = Integer.toHexString(b & 0xFF);
                      if (s.length()==1){
                          builder.append("0");
                      }
                      builder.append(s);
                  }

                  return builder.toString();

              }
          }

          2.在配置類中指定自定義密碼憑證匹配器

          /**
            * 加密
            * @return 加密對象
            * 如需使用自定義密碼憑證匹配器 返回自定義加密對象
            * 例如: return new MD5PasswordEncoder(); 
            */
          @Bean
          public PasswordEncoder passwordEncoder() {
              return new BCryptPasswordEncoder(); //Spring Security 自帶
          }

          登錄配置

          方式一 轉(zhuǎn)發(fā)

          http.formLogin()
              .usernameParameter("name") // 設(shè)置請求參數(shù)中,用戶名參數(shù)名稱。 默認(rèn)username
              .passwordParameter("pswd") // 設(shè)置請求參數(shù)中,密碼參數(shù)名稱。 默認(rèn)password
              .loginPage("/toLogin") // 當(dāng)用戶未登錄的時(shí)候,跳轉(zhuǎn)的登錄頁面地址是什么? 默認(rèn) /login
              .loginProcessingUrl("/login") // 用戶登錄邏輯請求地址是什么。 默認(rèn)是 /login
              .failureForwardUrl("/failure"); // 登錄失敗后,請求轉(zhuǎn)發(fā)的位置。Security請求轉(zhuǎn)發(fā)使用Post請求。默認(rèn)轉(zhuǎn)發(fā)到:loginPage?error
              .successForwardUrl("/toMain"); // 用戶登錄成功后,請求轉(zhuǎn)發(fā)到的位置。Security請求轉(zhuǎn)發(fā)使用POST請求。

          方式二 :重定向

          http.formLogin()
              .usernameParameter("name") // 設(shè)置請求參數(shù)中,用戶名參數(shù)名稱。 默認(rèn)username
              .passwordParameter("pswd") // 設(shè)置請求參數(shù)中,密碼參數(shù)名稱。 默認(rèn)password
              .loginPage("/toLogin") // 當(dāng)用戶未登錄的時(shí)候,跳轉(zhuǎn)的登錄頁面地址是什么? 默認(rèn) /login
              .loginProcessingUrl("/login") // 用戶登錄邏輯請求地址是什么。 默認(rèn)是 /login
           .defaultSuccessUrl("/toMain",true); //用戶登錄成功后,響應(yīng)重定向到的位置。GET請求。必須配置絕對地址。
            .failureUrl("/failure"); // 登錄失敗后,重定向的位置。

          方式三:自定義登錄處理器

          自定義登錄失敗邏輯處理器

          /*自定義登錄失敗處理器*/
          public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
              private  String url;
              private boolean isRedirect;


              public MyAuthenticationFailureHandler(String url, boolean isRedirect) {
                  this.url = url;
                  this.isRedirect = isRedirect;
              }

              @Override
              public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
                  if (isRedirect){
                      httpServletResponse.sendRedirect(url);
                  }else {
                      httpServletRequest.getRequestDispatcher(url).forward(httpServletRequest,httpServletResponse);
                  }
              }

          //get set 方法 省略

          自定義登錄成功邏輯處理器

          /**
           * 自定義登錄成功后處理器
           * 轉(zhuǎn)發(fā)重定向,有代碼邏輯實(shí)現(xiàn)
           * */
          public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
              private String url;
              private boolean isRedirect;

              public MyAuthenticationSuccessHandler(String url, boolean isRedirect) {
                  this.url = url;
                  this.isRedirect = isRedirect;
              }

              /**
               * @param request 請求對象 request.getRequestDispatcher.forward()
               * @param response 響應(yīng)對象 response.sendRedirect()
               * @param authentication 用戶認(rèn)證成功后的對象。其中報(bào)換用戶名權(quán)限結(jié)合,內(nèi)容是
               *                       自定義UserDetailsService
               * */
              @Override
              public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                  if (isRedirect){
                      response.sendRedirect(url);
                  }else {
                      request.getRequestDispatcher(url).forward(request,response);
                  }
              }

          //get set 方法 省略   
          http.formLogin()
              .usernameParameter("name") // 設(shè)置請求參數(shù)中,用戶名參數(shù)名稱。 默認(rèn)username
              .passwordParameter("pswd") // 設(shè)置請求參數(shù)中,密碼參數(shù)名稱。 默認(rèn)password
              .loginPage("/toLogin") // 當(dāng)用戶未登錄的時(shí)候,跳轉(zhuǎn)的登錄頁面地址是什么? 默認(rèn) /login
              .loginProcessingUrl("/login") // 用戶登錄邏輯請求地址是什么。 默認(rèn)是 /login

          登錄相關(guān)配置類

          @Configuration
          @EnableWebSecurity
          public class SecurityConfig extends WebSecurityConfigurerAdapter {

              @Autowired
              private  UserSecurity userSecurity;
              @Autowired
              private PersistentTokenRepository persistentTokenRepository;


              /**
               * 加密
               * @return 加密對象
               * 如需使用自定義加密邏輯 返回自定義加密對象
               * return new MD5PasswordEncoder(); return new SimplePasswordEncoder();
               */
              @Bean
              public PasswordEncoder passwordEncoder() {
                  return new BCryptPasswordEncoder(); //Spring Security 自帶
              }

              @Override
              protected void configure(HttpSecurity http) throws Exception {
                  // 配置登錄請求相關(guān)內(nèi)容。
                  http.formLogin()
                      .loginPage("/toLogin") // 當(dāng)用戶未登錄的時(shí)候,跳轉(zhuǎn)的登錄頁面地址是什么? 默認(rèn) /login
                      .usernameParameter("name") // 設(shè)置請求參數(shù)中,用戶名參數(shù)名稱。 默認(rèn)username
                      .passwordParameter("pswd") // 設(shè)置請求參數(shù)中,密碼參數(shù)名稱。 默認(rèn)password
                      .loginProcessingUrl("/login") //設(shè)置登錄 提交表單數(shù)據(jù)訪問請求地址
                      .defaultSuccessUrl("/toMain")   
                      .failureUrl("/toLogin");
                   //.successForwardUrl("/toMain")
                   //.failureForwardUrl("/toLogin");
                      //.successHandler(new LoginSuccessHandler("/toMain"true)) //自定義登錄成功處理器
                          //.failureHandler(new LoginErrorHandler("/toLogin"true));

                  http.authorizeRequests()
                      //.antMatchers("/toLogin").anonymous() //只能匿名用戶訪問
                      .antMatchers("/toLogin""/register""/login""/favicon.ico").permitAll() // /toLogin請求地址,可以隨便訪問。
                      .antMatchers("/**/*.js").permitAll() // 授予所有目錄下的所有.js文件可訪問權(quán)限
                      .regexMatchers(".*[.]css").permitAll() // 授予所有目錄下的所有.css文件可訪問權(quán)限
                      .anyRequest().authenticated(); // 任意的請求,都必須認(rèn)證后才能訪問。


                  // 配置退出登錄
                  http.logout()
                          .invalidateHttpSession(true) // 回收HttpSession對象。退出之前調(diào)用HttpSession.invalidate() 默認(rèn) true
                          .clearAuthentication(true) // 退出之前,清空Security記錄的用戶登錄標(biāo)記。 默認(rèn) true
                          // .addLogoutHandler() // 增加退出處理器。
                          .logoutSuccessUrl("/") // 配置退出后,進(jìn)入的請求地址。 默認(rèn)是loginPage?logout
                          .logoutUrl("/logout"); // 配置退出登錄的路徑地址。和頁面請求地址一致即可。

                  // 關(guān)閉CSRF安全協(xié)議。
                  // 關(guān)閉是為了保證完整流程的可用。
                  http.csrf().disable();
              }


             @Bean
             public PersistentTokenRepository persistentTokenRepository(DataSource dataSource){
                  JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
                  jdbcTokenRepository.setDataSource(dataSource);
                  //jdbcTokenRepository.setCreateTableOnStartup(true);
                  return jdbcTokenRepository;
              }
          }

          角色權(quán)限

          ?

          「hasAuthority(String)」 判斷角色是否具有特定權(quán)限

          ?

          http.authorizeRequests().antMatchers("/main1.html").hasAuthority("admin")

          ?

          「hasAnyAuthority(String ...)」 如果用戶具備給定權(quán)限中某一個,就允許訪問

          ?

          http.authorizeRequests().antMatchers("/admin/read").hasAnyAuthority("xxx","xxx"

          ?

          「hasRole(String)」 如果用戶具備給定角色就允許訪問。否則出現(xiàn)403

          ?

          //請求地址為/admin/read的請求,必須登錄用戶擁有'管理員'角色才可訪問
          http.authorizeRequests().antMatchers("/admin/read").hasRole("管理員"

          ?

          「hasAnyRole(String ...)」 如果用戶具備給定角色的任意一個,就允許被訪問

          ?

          //用戶擁有角色是管理員 或 訪客 可以訪問 /guest/read
          http.authorizeRequests().antMatchers("/guest/read").hasAnyRole("管理員""訪客")

          ?

          「hasIpAddress(String)」 請求是指定的IP就運(yùn)行訪問

          ?

          //ip 是127.0.0.1 的請求 可以訪問/ip
          http.authorizeRequests().antMatchers("/ip").hasIpAddress("127.0.0.1")

          403 權(quán)限不足頁面處理

          1.編寫類實(shí)現(xiàn)接口「AccessDeniedHandler」

          /**
           * @describe  403 權(quán)限不足
           * @author: AnyWhere
           * @date 2021/4/18 20:57
           */
          @Component
          public class MyAccessDeniedHandler implements AccessDeniedHandler {
              @Override
              public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) 
                      throws IOException, ServletException {

                  response.setStatus(HttpServletResponse.SC_OK);

                  response.setContentType("text/html;charset=UTF-8");

                  response.getWriter().write(
                          "<html>" +
                                  "<body>" +
                                  "<div style='width:800px;text-align:center;margin:auto;font-size:24px'>" +
                                  "權(quán)限不足,請聯(lián)系管理員" +
                                  "</div>" +
                                  "</body>" +
                                  "</html>"

                  );

                  response.getWriter().flush();//刷新緩沖區(qū)
              }
          }

          2.配置類中配置exceptionHandling

          // 配置403訪問錯誤處理器。
          http.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler);/

          RememberMe(記住我)

          @Configuration
          @EnableWebSecurity
          public class SecurityConfig extends WebSecurityConfigurerAdapter {
            @Override
            protected void configure(HttpSecurity http) throws Exception {
              //配置記住密碼
              http.rememberMe()
                  .rememberMeParameter("remember-me") // 修改請求參數(shù)名。 默認(rèn)是remember-me
                  .tokenValiditySeconds(14*24*60*60) // 設(shè)置記住我有效時(shí)間。單位是秒。默認(rèn)是14天
                  .rememberMeCookieName("remember-me") // 修改remember me的cookie名稱。默認(rèn)是remember-me
                  .tokenRepository(persistentTokenRepository) // 配置用戶登錄標(biāo)記的持久化工具對象。
                  .userDetailsService(userSecurity); // 配置自定義的UserDetailsService接口實(shí)現(xiàn)類對象

            }
            @Bean
            public PersistentTokenRepository persistentTokenRepository(DataSource dataSource){
               JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
               jdbcTokenRepository.setDataSource(dataSource);
               //jdbcTokenRepository.setCreateTableOnStartup(true);
               return jdbcTokenRepository;
            }
          }   

          Spring Security 注解

          @Secured

          ?

          角色校驗(yàn) ,請求到來訪問控制單元方法時(shí)必須包含XX角色才能訪問

          角色必須添加ROLE_前綴

          ?

            @Secured({"ROLE_管理員","ROLE_訪客"})
            @RequestMapping("/toMain")
            public String toMain(){
                return "main";
            }

          使用注解@Secured需要在配置類中添加注解 使@Secured注解生效

          @EnableGlobalMethodSecurity(securedEnabled = true)

          @PreAuthorize

          ?

          權(quán)限檢驗(yàn),請求到來訪問控制單元之前必須包含xx權(quán)限才能訪問,控制單元方法執(zhí)行前進(jìn)行角色校驗(yàn)

          ?

             /**
               * [ROLE_管理員, admin:read, admin:write, all:login, all:logout, all:error, all:toMain]
               * @PreAuthorize   角色 、權(quán)限 校驗(yàn) 方法執(zhí)行前進(jìn)行角色校驗(yàn)
               *
               *  hasAnyAuthority() 
               *  hasAuthority()
               *
               *  hasPermission()
               *
               *
               *  hasRole()   
               *  hasAnyRole()
               * */

              @PreAuthorize("hasAnyRole('ROLE_管理員','ROLE_訪客')")
              @RequestMapping("/toMain")
              @PreAuthorize("hasAuthority('admin:write')")
              public String toMain(){
                  return "main";
              }

          使用@PreAuthorize@PostAuthorize 需要在配置類中配置注解@EnableGlobalMethodSecurity 才能生效

          @EnableGlobalMethodSecurity(prePostEnabled = true)

          @PostAuthorize

          ?

          權(quán)限檢驗(yàn),請求到來訪問控制單元之后必須包含xx權(quán)限才能訪問 ,控制單元方法執(zhí)行完后進(jìn)行角色校驗(yàn)

          ?

             /**
               * [ROLE_管理員, admin:read, admin:write, all:login, all:logout, all:error, all:toMain]
               * @PostAuthorize  角色 、權(quán)限 校驗(yàn) 方法執(zhí)行后進(jìn)行角色校驗(yàn)
               *
               *  hasAnyAuthority()
               *  hasAuthority()
               *  hasPermission()
               *  hasRole()
               *  hasAnyRole()
               * */
              @PostAuthorize("hasRole('ROLE_管理員')")
              @RequestMapping("/toMain")
              @PreAuthorize("hasAuthority('admin:write')")
              public String toMain(){
                  return "main";
              }

          Spring Security 整合Thymeleaf 進(jìn)行權(quán)限校驗(yàn)

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

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

          Spring Security中CSRF

          什么是CSRF?

          CSRF(Cross-site request forgery)跨站請求偽造,也被稱為“One Click Attack” 或者Session Riding。通過偽造用戶請求訪問受信任站點(diǎn)的非法請求訪問。

          跨域:只要網(wǎng)絡(luò)協(xié)議,ip地址,端口中任何一個不相同就是跨域請求。

          客戶端與服務(wù)進(jìn)行交互時(shí),由于http協(xié)議本身是無狀態(tài)協(xié)議,所以引入了cookie進(jìn)行記錄客戶端身份。在cookie中會存放session id用來識別客戶端身份的。在跨域的情況下,session id可能被第三方惡意劫持,通過這個session id向服務(wù)端發(fā)起請求時(shí),服務(wù)端會認(rèn)為這個請求是合法的,可能發(fā)生很多意想不到的事情。

          通俗解釋:

          CSRF就是別的網(wǎng)站非法獲取我們網(wǎng)站Cookie值,我們項(xiàng)目服務(wù)器是無法區(qū)分到底是不是我們的客戶端,只有請求中有Cookie,認(rèn)為是自己的客戶端,所以這個時(shí)候就出現(xiàn)了CSRF。


          記得點(diǎn)「」和「在看」↓

          愛你們


          瀏覽 84
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  先锋亚洲资源 | 99好逼网| 天天插一插 | 国产青草视频在线观看 | 91久久久久久久久久久 |