中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

SpringBoot安全管理之Spring?Security如何配置

發布時間:2022-08-13 10:04:25 來源:億速云 閱讀:266 作者:iii 欄目:開發技術

這篇文章主要介紹了SpringBoot安全管理之Spring Security如何配置的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇SpringBoot安全管理之Spring Security如何配置文章都會有所收獲,下面我們一起來看看吧。

    在 Java 開發領域常見的安全框架有 Shiro 和 Spring Security。Shiro 是一個輕量級的安全管理框架,提供了認證、授權、會話管理、密碼管理、緩存管理等功能。Spring Security 是一個相對復雜的安全管理框架,功能比 Shiro 更加強大,權限控制細粒度更高,對 OAuth 2 的支持也很友好,又因為 Spring Security 源自 Spring 家族,因此可以和 Spring 框架無縫整合,特別是 Spring Boot 中提供的自動化配置方案,可以讓 Spring Security 的使用更加便捷。

    Spring Security 的基本配置

    基本用法

    1. 創建項目添加依賴

    創建一個 Spring Boot 項目,然后添加 spring-boot-starter-security 依賴即可

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    2. 添加 hello 接口
    @RestController
    public class HelloController {
        @GetMapping("/hello")
        public String hello() {
            return "hello";
        }
    }
    3. 啟動項目測試

    啟動成功后,訪問 /hello 接口就會自動跳轉到登錄頁面,這個登錄頁面是由 Spring Security 提供的

    SpringBoot安全管理之Spring?Security如何配置

    默認的用戶名是 user ,默認的登錄密碼則在每次啟動項目時隨機生成,查看項目啟動日志

    Using generated security password: 4f845a17-7b09-479c-8701-48000e89d364

    登錄成功后,用戶就可以訪問 /hello 接口了

    配置用戶名和密碼

    如果開發者對默認的用戶名和密碼不滿意,可以在 application.properties 中配置默認的用戶名、密碼以及用戶角色

    spring.security.user.name=tangsan
    spring.security.user.password=tangsan
    spring.security.user.roles=admin

    基于內存的認證

    開發者也可以自定義類繼承自 WebSecurityConfigurer,進而實現對 Spring Security 更多的自定義配置,例如基于內存的認證,配置方式如下:

    @Configuration
    public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
        @Bean
        PasswordEncoder passwordEncoder() {
            return NoOpPasswordEncoder.getInstance();
        }
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication()
                    .withUser("admin").password("123123").roles("ADMIN", "USER")
                    .and()
                    .withUser("tangsan").password("123123").roles("USER");
        }
    }

    代碼解釋:

    • 自定義 MyWebSecurityConfig 繼承自 WebSecurityConfigurerAdapter ,并重寫 configure(AuthenticationManagerBuilder auth) 方法,在該方法中配置兩個用戶,一個用戶是 admin ,具備兩個角色 ADMIN、USER;另一個用戶是 tangsan ,具備一個角色 USER

    • 此處使用的 Spring Security 版本是 5.0.6 ,在 Spring Security 5.x 中引入了多種密碼加密方式,開發者必須指定一種,此處使用 NoOpPasswordEncoder ,即不對密碼進行加密

    注意:基于內存的用戶配置,在配置角色時不需要添加 “ROLE_” 前綴,這點和后面 10.2 節中基于數據庫的認證有差別。

    配置完成后,重啟項目,就可以使用這里配置的兩個用戶進行登錄了。

    HttpSecurity

    雖然現在可以實現認證功能,但是受保護的資源都是默認的,而且不能根據實際情況進行角色管理,如果要實現這些功能,就需要重寫 WebSecurityConfigurerAdapter 中的另一個方法

    @Configuration
    public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
        @Bean
        PasswordEncoder passwordEncoder() {
            return NoOpPasswordEncoder.getInstance();
        }
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication()
                    .withUser("root").password("123123").roles("ADMIN", "DBA")
                    .and()
                    .withUser("admin").password("123123").roles("ADMIN", "USER")
                    .and()
                    .withUser("tangsan").password("123123").roles("USER");
        }
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .antMatchers("/admin/**")
                    .hasRole("ADMIN")
                    .antMatchers("/user/**")
                    .access("hasAnyRole('ADMIN','USER')")
                    .antMatchers("/db/**")
                    .access("hasRole('ADMIN') and hasRole('DBA')")
                    .anyRequest()
                    .authenticated()
                    .and()
                    .formLogin()
                    .loginProcessingUrl("/login")
                    .permitAll()
                    .and()
                    .csrf()
                    .disable();
        }
    }

    代碼解釋:

    • 首先配置了三個用戶,root 用戶具備 ADMIN 和 DBA 的角色,admin 用戶具備 ADMIN 和 USER 角色,tangsan 用于具備 USER 角色

    • 調用 authorizeRequests() 方法開啟 HttpSecurity 的配置,antMatchers() ,hasRole() ,access() 方法配置訪問不同的路徑需要不同的用戶及角色

    • anyRequest(),authenticated() 表示出了前面定義的之外,用戶訪問其他的 URL 都必須認證后訪問

    • formLogin(),loginProcessingUrl(“/login”),permitAll(),表示開啟表單登錄,前面看到的登錄頁面,同時配置了登錄接口為 /login 即可以直接調用 /login 接口,發起一個 POST 請求進行登錄,登錄參數中用戶名必須命名為 username ,密碼必須命名為 password,配置 loginProcessingUrl 接口主要是方便 Ajax 或者移動端調用登錄接口。最后還配置了 permitAll,表示和登錄相關的接口都不需要認證即可訪問。

    配置完成后,在 Controller 中添加如下接口進行測試:

    @RestController
    public class HelloController {
        @GetMapping("/admin/hello")
        public String admin() {
            return "hello admin";
        }
        @GetMapping("/user/hello")
        public String user() {
            return "hello user";
        }
        @GetMapping("/db/hello")
        public String dba() {
            return "hello dba";
        }
        @GetMapping("/hello")
        public String hello() {
            return "hello";
        }
    }

    根據上文配置,“/admin/hello” 接口 root 和 admin 用戶具有訪問權限;“/user/hello” 接口 admin 和 tangsan 用戶具有訪問權限;“/db/hello” 只有 root 用戶有訪問權限。瀏覽器中的測試很容易,這里不再贅述。

    登錄表單詳細配置

    目前為止,登錄表單一直使用 Spring Security 提供的頁面,登錄成功后也是默認的頁面跳轉,但是,前后端分離已經成為企業級應用開發的主流,在前后端分離的開發方式中,前后端的數據交互通過 JSON 進行,這時,登錄成功后就不是頁面跳轉了,而是一段 JSON 提示。要實現這些功能,只需要繼續完善上文的配置

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/admin/**")
            .hasRole("ADMIN")
            .antMatchers("/user/**")
            .access("hasAnyRole('ADMIN','USER')")
            .antMatchers("/db/**")
            .access("hasRole('ADMIN') and hasRole('DBA')")
            .anyRequest()
            .authenticated()
            .and()
            .formLogin()
            .loginPage("/login_page")
            .loginProcessingUrl("/login")
            .usernameParameter("name")
            .passwordParameter("passwd")
            .successHandler(new AuthenticationSuccessHandler() {
                @Override
                public void onAuthenticationSuccess(HttpServletRequest req,
                                                    HttpServletResponse resp,
                                                    Authentication auth)
                    throws IOException {
                    Object principal = auth.getPrincipal();
                    resp.setContentType("application/json;charset=utf-8");
                    PrintWriter out = resp.getWriter();
                    resp.setStatus(200);
                    Map<String, Object> map = new HashMap<>();
                    map.put("status", 200);
                    map.put("msg", principal);
                    ObjectMapper om = new ObjectMapper();
                    out.write(om.writeValueAsString(map));
                    out.flush();
                    out.close();
                }
            })
            .failureHandler(new AuthenticationFailureHandler() {
                @Override
                public void onAuthenticationFailure(HttpServletRequest req,
                                                    HttpServletResponse resp,
                                                    AuthenticationException e)
                    throws IOException {
                    resp.setContentType("application/json;charset=utf-8");
                    PrintWriter out = resp.getWriter();
                    resp.setStatus(401);
                    Map<String, Object> map = new HashMap<>();
                    map.put("status", 401);
                    if (e instanceof LockedException) {
                        map.put("msg", "賬戶被鎖定,登錄失敗!");
                    } else if (e instanceof BadCredentialsException) {
                        map.put("msg", "賬戶名或密碼輸入錯誤,登錄失敗!");
                    } else if (e instanceof DisabledException) {
                        map.put("msg", "賬戶被禁用,登錄失敗!");
                    } else if (e instanceof AccountExpiredException) {
                        map.put("msg", "賬戶已過期,登錄失敗!");
                    } else if (e instanceof CredentialsExpiredException) {
                        map.put("msg", "密碼已過期,登錄失敗!");
                    } else {
                        map.put("msg", "登錄失敗!");
                    }
                    ObjectMapper om = new ObjectMapper();
                    out.write(om.writeValueAsString(map));
                    out.flush();
                    out.close();
                }
            })
            .permitAll()
            .and()
            .csrf()
            .disable();
    }

    代碼解釋:

    • loginPage(“/login_page”) 表示如果用戶未獲授權就訪問一個需要授權才能訪問的接口,就會自動跳轉到 login_page 頁面讓用戶登錄,這個 login_page 就是開發者自定義的登錄頁面,而不再是 Spring Security 提供的默認登錄頁

    • loginProcessingUrl(“/login”) 表示登錄請求處理接口,無論是自定義登錄頁面還是移動端登錄,都需要使用該接口

    • usernameParameter(“name”),passwordParameter(“passwd”) 定義了認證所需要的用戶名和密碼的參數,默認用戶名參數是 username,密碼參數是 password,可以在這里定義

    • successHandler() 方法定義了登錄成功的處理邏輯。用戶登錄成功后可以跳轉到某一個頁面,也可以返回一段 JSON ,這個要看具體業務邏輯,此處假設是第二種,用戶登錄成功后,返回一段登錄成功的 JSON 。onAuthenticationSuccess 方法的第三個參數一般用來獲取當前登錄用戶的信息,在登錄后,可以獲取當前登錄用戶的信息一起返回給客戶端

    • failureHandler 方法定義了登錄失敗的處理邏輯,和登錄成功類似,不同的是,登錄失敗的回調方法里有一個 AuthenticationException 參數,通過這個異常參數可以獲取登錄失敗的原因,進而給用戶一個明確的提示

    配置完成后,使用 Postman 進行測試

    SpringBoot安全管理之Spring?Security如何配置

    如果登錄失敗也會有相應的提示

    SpringBoot安全管理之Spring?Security如何配置

    注銷登錄配置

    如果想要注銷登錄,也只需要提供簡單的配置即可

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/admin/**")
            .hasRole("ADMIN")
            .antMatchers("/user/**")
            .access("hasAnyRole('ADMIN','USER')")
            .antMatchers("/db/**")
            .access("hasRole('ADMIN') and hasRole('DBA')")
            .anyRequest()
            .authenticated()
            .and()
            .formLogin()
            .loginPage("/login_page")
            .loginProcessingUrl("/login")
            .usernameParameter("name")
            .passwordParameter("passwd")
            .successHandler(new AuthenticationSuccessHandler() {
                @Override
                public void onAuthenticationSuccess(HttpServletRequest req,
                                                    HttpServletResponse resp,
                                                    Authentication auth)
                    throws IOException {
                    Object principal = auth.getPrincipal();
                    resp.setContentType("application/json;charset=utf-8");
                    PrintWriter out = resp.getWriter();
                    resp.setStatus(200);
                    Map<String, Object> map = new HashMap<>();
                    map.put("status", 200);
                    map.put("msg", principal);
                    ObjectMapper om = new ObjectMapper();
                    out.write(om.writeValueAsString(map));
                    out.flush();
                    out.close();
                }
            })
            .failureHandler(new AuthenticationFailureHandler() {
                @Override
                public void onAuthenticationFailure(HttpServletRequest req,
                                                    HttpServletResponse resp,
                                                    AuthenticationException e)
                    throws IOException {
                    resp.setContentType("application/json;charset=utf-8");
                    PrintWriter out = resp.getWriter();
                    resp.setStatus(401);
                    Map<String, Object> map = new HashMap<>();
                    map.put("status", 401);
                    if (e instanceof LockedException) {
                        map.put("msg", "賬戶被鎖定,登錄失敗!");
                    } else if (e instanceof BadCredentialsException) {
                        map.put("msg", "賬戶名或密碼輸入錯誤,登錄失敗!");
                    } else if (e instanceof DisabledException) {
                        map.put("msg", "賬戶被禁用,登錄失敗!");
                    } else if (e instanceof AccountExpiredException) {
                        map.put("msg", "賬戶已過期,登錄失敗!");
                    } else if (e instanceof CredentialsExpiredException) {
                        map.put("msg", "密碼已過期,登錄失敗!");
                    } else {
                        map.put("msg", "登錄失敗!");
                    }
                    ObjectMapper om = new ObjectMapper();
                    out.write(om.writeValueAsString(map));
                    out.flush();
                    out.close();
                }
            })
            .permitAll()
            .and()
            .logout()
            .logoutUrl("/logout")
            .clearAuthentication(true)
            .invalidateHttpSession(true)
            .addLogoutHandler(new LogoutHandler() {
                @Override
                public void logout(HttpServletRequest req,
                                   HttpServletResponse resp,
                                   Authentication auth) {
                }
            })
            .logoutSuccessHandler(new LogoutSuccessHandler() {
                @Override
                public void onLogoutSuccess(HttpServletRequest req,
                                            HttpServletResponse resp,
                                            Authentication auth)
                    throws IOException {
                    resp.sendRedirect("/login_page");
                }
            })
            .and()
            .csrf()
            .disable();
    }

    代碼解釋:

    • logout() 表示開啟注銷登錄的配置

    • logoutUrl(“/logout”) 表示注銷登錄請求 URL 為 /logout ,默認也是 /logout

    • clearAuthentication(true) 表示是否清楚身份認證信息,默認為 true

    • invalidateHttpSession(true) 表示是否使 Session 失效,默認為 true

    • addLogoutHandler 方法中完成一些數據清楚工作,例如 Cookie 的清楚

    • logoutSuccessHandler 方法用于處理注銷成功后的業務邏輯,例如返回一段 JSON 提示或者跳轉到登錄頁面等

    多個 HttpSecurity

    如果業務比較復雜,也可以配置多個 HttpSecurity ,實現對 WebSecurityConfigurerAdapter 的多次擴展

    @Configuration
    public class MultiHttpSecurityConfig {
        @Bean
        PasswordEncoder passwordEncoder() {
            return NoOpPasswordEncoder.getInstance();
        }
        @Autowired
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication()
                    .withUser("admin").password("123123").roles("ADMIN", "USER")
                    .and()
                    .withUser("tangsan").password("123123").roles("USER");
        }
        @Configuration
        @Order(1)
        public static class AdminSecurityConfig extends WebSecurityConfigurerAdapter{
            @Override
            protected void configure(HttpSecurity http) throws Exception {
                http.antMatcher("/admin/**").authorizeRequests()
                        .anyRequest().hasRole("ADMIN");
            }
        }
        @Configuration
        public static class OtherSecurityConfig extends WebSecurityConfigurerAdapter{
            @Override
            protected void configure(HttpSecurity http) throws Exception {
                http.authorizeRequests()
                        .anyRequest().authenticated()
                        .and()
                        .formLogin()
                        .loginProcessingUrl("/login")
                        .permitAll()
                        .and()
                        .csrf()
                        .disable();
            }
        }
    }

    代碼解釋:

    • 配置多個 HttpSecurity 時,MultiHttpSecurityConfig 不需要繼承 WebSecurityConfigurerAdapter ,在 MultiHttpSecurityConfig 中創建靜態內部類繼承 WebSecurityConfigurerAdapter 即可,靜態內部類上添加 @Configuration 注解和 @Order注解,數字越大優先級越高,未加 @Order 注解的配置優先級最低

    • AdminSecurityConfig 類表示該類主要用來處理 “/admin/**” 模式的 URL ,其它 URL 將在 OtherSecurityConfig 類中處理

    密碼加密

    1. 為什么要加密

    2. 加密方案

    Spring Security 提供了多種密碼加密方案,官方推薦使用 BCryptPasswordEncoder,BCryptPasswordEncoder 使用 BCrypt 強哈希函數,開發者在使用時可以選擇提供 strength 和 SecureRandom 實例。strength 越大,密碼的迭代次數越多,密鑰迭代次數為 2^strength 。strength 取值在 4~31 之間,默認為 10 。

    3. 實踐

    只需要修改上文配置的 PasswordEncoder 這個 Bean 的實現即可

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

    參數 10 就是 strength ,即密鑰的迭代次數(也可以不配置,默認為 10)。

    使用以下方式獲取加密后的密碼。

    public static void main(String[] args) {
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(10);
        String encode = bCryptPasswordEncoder.encode("123123");
        System.out.println(encode);
    }

    修改配置的內存用戶的密碼

    auth.inMemoryAuthentication()
        .withUser("admin")
        .password("$2a$10$.hZESNfpLSDUnuqnbnVaF..Xb2KsAqwvzN7hN65Gd9K0VADuUbUzy")
        .roles("ADMIN", "USER")
        .and()
        .withUser("tangsan")
        .password("$2a$10$4LJ/xgqxSnBqyuRjoB8QJeqxmUeL2ynD7Q.r8uWtzOGs8oFMyLZn2")
        .roles("USER");

    雖然 admin 和 tangsan 加密后的密碼不一樣,但是明文都是 123123 配置完成后,使用 admin/123123,或 tangsan/123123 就可以實現登錄,一般情況下,用戶信息是存儲在數據庫中的,因此需要用戶注冊時對密碼進行加密處理

    @Service
    public class RegService {
        public int reg(String username, String password) {
            BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(10);
            String encodePasswod = encoder.encode(password);
            return saveToDb(username, encodePasswod);
        }
        private int saveToDb(String username, String encodePasswod) {
            // 業務處理
            return 0;
        }
    }

    用戶將密碼從前端傳來之后,通過 BCryptPasswordEncoder 實例中的 encode 方法對密碼進行加密處理,加密完成后將密文存入數據庫。

    方法安全

    上文介紹的認證和授權都是基于 URL 的,開發者也可通過注解來靈活配置方法安全,使用相關注解,首先要通過 @EnableGlobalMethodSecurity 注解開啟基于注解的安全配置

    @Configuration
    @EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
    public class MultiHttpSecurityConfig{
    }

    代碼解釋:

    • prePostEnabled = true 會解鎖 @PreAuthorize 和 @PostAuthorize 兩個注解, @PreAuthorize 注解會在方法執行前進行驗證,而 @PostAuthorize 注解在方法執行后進行驗證

    • securedEnabled = true 會解鎖 @Secured 注解

    開啟注解安全后,創建一個 MethodService 進行測試

    @Service
    public class MethodService {
        @Secured("ROLE_ADMIN")
        public String admin() {
            return "hello admin";
        }
        @PreAuthorize("hasRole('ADMIN') and hasRole('DBA')")
        public String dba() {
            return "hello dba";
        }
        @PreAuthorize("hasAnyRole('ADMIN','DBA','USER')")
        public String user() {
            return "user";
        }
    }

    代碼解釋:

    • @Secured(“ROLE_ADMIN”) 注解表示訪問該方法需要 ADMIN 角色,注意這里需要在角色前加一個前綴 ROLE_

    • @PreAuthorize(“hasRole(&lsquo;ADMIN&rsquo;) and hasRole(&lsquo;DBA&rsquo;)”) 注解表示訪問該方法既需要 ADMIN 角色又需要 DBA 角色

    • @PreAuthorize(“hasAnyRole(&lsquo;ADMIN&rsquo;,&lsquo;DBA&rsquo;,&lsquo;USER&rsquo;)”) 表示訪問該方法需要 ADMIN 、DBA 或 USER 角色中至少一個

    • @PostAuthorize 和 @PreAuthorize 中都可以使用基于表達式的語法

    最后在 Controller 中注入 Service 并調用 Service 中的方法進行測試

    @RestController
    public class HelloController {
        @Autowired
        MethodService methodService;
        @GetMapping("/hello")
        public String hello() {
            String user = methodService.user();
            return user;
        }
        @GetMapping("/hello2")
        public String hello2() {
            String admin = methodService.admin();
            return admin;
        }
        @GetMapping("/hello3")
        public String hello3() {
            String dba = methodService.dba();
            return dba;
        }
    }

    admin 訪問 hello

    SpringBoot安全管理之Spring?Security如何配置

    admin 訪問 hello2

    SpringBoot安全管理之Spring?Security如何配置

    admin 訪問 hello3

    SpringBoot安全管理之Spring?Security如何配置

    關于“SpringBoot安全管理之Spring Security如何配置”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“SpringBoot安全管理之Spring Security如何配置”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業資訊頻道。

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    廉江市| 安达市| 康平县| 都匀市| 扎囊县| 新龙县| 平和县| 五寨县| 类乌齐县| 朝阳区| 西城区| 邹平县| 民乐县| 喀喇沁旗| 花莲市| 讷河市| 临武县| 安多县| 邮箱| 仪征市| 厦门市| 沁阳市| 贵定县| 中方县| 岳西县| 文水县| 太原市| 大兴区| 罗甸县| 铅山县| 饶阳县| 错那县| 诏安县| 新巴尔虎左旗| 金塔县| 遂宁市| 建瓯市| 松桃| 汤阴县| 莎车县| 商城县|