<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在 Java 開發領域常見的安全框架有 Shiro 和 Spring Security。Shiro 是一個輕量級的安全管理框架,提供了認證、授權、對談管理、密碼管理、快取管理等功能。Spring Security 是一個相對複雜的安全管理框架,功能比 Shiro 更加強大,許可權控制細粒度更高,對 OAuth 2 的支援也很友好,又因為 Spring Security 源自 Spring 家族,因此可以和 Spring 框架無縫整合,特別是 Spring Boot 中提供的自動化設定方案,可以讓 Spring Security 的使用更加便捷。
建立一個 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>
@RestController public class HelloController { @GetMapping("/hello") public String hello() { return "hello"; } }
啟動成功後,存取 /hello 介面就會自動跳轉到登入頁面,這個登入頁面是由 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"); } }
程式碼解釋:
注意:基於記憶體的使用者設定,在設定角色時不需要新增 “ROLE_” 字首,這點和後面 10.2 節中基於資料庫的認證有差別。
設定完成後,重啟專案,就可以使用這裡設定的兩個使用者進行登入了。
雖然現在可以實現認證功能,但是受保護的資源都是預設的,而且不能根據實際情況進行角色管理,如果要實現這些功能,就需要重寫 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(); } }
程式碼解釋:
設定完成後,在 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(); }
程式碼解釋:
設定完成後,使用 Postman 進行測試
如果登入失敗也會有相應的提示
如果想要登出登入,也只需要提供簡單的設定即可
@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(); }
程式碼解釋:
如果業務比較複雜,也可以設定多個 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(); } } }
程式碼解釋:
略
Spring Security 提供了多種密碼加密方案,官方推薦使用 BCryptPasswordEncoder,BCryptPasswordEncoder 使用 BCrypt 強雜湊函數,開發者在使用時可以選擇提供 strength 和 SecureRandom 範例。strength 越大,密碼的迭代次數越多,金鑰迭代次數為 2^strength 。strength 取值在 4~31 之間,預設為 10 。
只需要修改上文設定的 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{ }
程式碼解釋:
開啟註解安全後,建立一個 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"; } }
程式碼解釋:
最後在 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
admin 存取 hello2
admin 存取 hello3
到此這篇關於SpringBoot淺析安全管理之Spring Security設定的文章就介紹到這了,更多相關SpringBoot Spring Security內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45