簡介:
Spring Boot針對Spring Security提供了自動化配置方案,因此可以使Spring Security非常容易地整合進Spring Boot項目中,這也是在Spring Boot項目中使用Spring Security的優勢。
1.添加依賴
pom.xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
spring-boot-starter-security添加依賴后項目中所有資源都被保護起來了
2/添加hello接口
@RestController public class HelloController { @GetMapping("/hello") public String hello() { String user = methodService.user(); return user; } }
3.啟動項目
訪問:http://localhost:8080/hello
項目自動跳轉到這個由spring security提供的頁面
默認用戶名user,密碼:控制台隨機字符串
登陸后:
4.更多:
也可以配置默認的用戶名和密碼還有用戶角色
重啟-登陸,可以看到用戶名密碼等已經被更改
基於內存的認證:
當然,開發者也可以自定義類繼承自WebSecurityConfigurerAdapter,進而實現對Spring Security更多的自定義配置,例如基於內存的認證,配置方式如下:
自定義類繼承WebSecurityConfigurerAdapter類,重寫configure方法,在其中增加了兩個用戶,配置用戶名,密碼,角色。
至於加密,使用了NoOpPasswordEncoder即不加密
HttpSecurity
雖然現在可以實現認證功能,但是受保護的資源都是默認的,而且也不能根據實際情況進行角色管理,如果要實現這些功能,就需要重寫WebSecurityConfigurerAdapter 中的另一個方法configure,參數可以看到是HttpSecurity.
在第一個configure方法中添加3個角色,root擁有admin和dba,admin擁有admin,user,cc擁有user
在第二個configure方法中,調用authorizeRequests方法開啟HttpSecurity配置,
.antMatchers("/admin/**")
.hasRole("ADMIN")
表示訪問/admin/路徑的必須要admin角色,后面兩個也一樣道理。
.anyRequest()
.authenticated()
表示除了前面定義的url,后面的都得認證后訪問(登陸后訪問)
.formLogin()
.loginProcessingUrl("/url") .permitAll()
表示開啟表單登陸,就是一開始看到的登陸界面,登陸url為/login,permitAll表示和登陸相關的接口不需要認證
.csrf()
.disable();
表示關閉csrf(Cross-site request forgery)
@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("123").roles("ADMIN","DBA") .and() .withUser("admin").password("123").roles("ADMIN","USER") .and() .withUser("cc").password("123").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("hasAnyRole('ADMIN') and hasRole('DBA')")
.anyRequest() .authenticated() .and()
.formLogin() .loginProcessingUrl("/url") .permitAll() .and()
.csrf() .disable(); } }
在controller:上面的配置,url為/admin/的需要由admin角色,/user/的需要admin或者user都可,/db/的需要admin和dba角色才可以
@GetMapping("/admin/hello") public String hello2(){ return "admin"; } @GetMapping("/db/hello") public String hello3(){ return "db"; } @GetMapping("/user/hello") public String hello4(){ return "user"; }
登陸表單詳細配置:
迄今為止,登錄表單一直使用Spring Security提供的頁面,登錄成功后也是默認的頁面跳轉,但是,前后端分離正在成為企業級應用開發的主流,在前后端分離的開發方式中,前后端的數據交互通過JSON進行,這時,登錄成功后就不是頁面跳轉了,而是一段JSON提示。要實現這些功能,只需要繼續完善上文的配置,代碼如下:
@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("123").roles("ADMIN", "DBA") .and() .withUser("admin").password("123").roles("ADMIN", "USER") .and() .withUser("sang").password("123").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() .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") //注銷登陸請求url .clearAuthentication(true) //清除身份信息 .invalidateHttpSession(true) //session失效 .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,並且加密,方法安全
配置多個httpSecurity不需要繼承WebSecurityConfigurerAdapter,內部類去繼承即可,使用@Configuration和@Order注解優先配置
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
//prePostEnabled=true會解鎖@PreAuthorize和@PostAuthorize兩個注解,顧名思義,@PreAuthorize注解會在方法執行前進行驗證,而@PostAuthorize 注解在方法執行后進行驗證。
//securedEnabled=true會解鎖@Secured注解。
public class MultiHttpSecurityConfig{ @Bean PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Autowired protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("root") .password("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq") //密碼已經加密 .roles("ADMIN", "DBA") .and() .withUser("admin") .password("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq") .roles("ADMIN", "USER") .and() .withUser("sang") .password("$2a$10$eUHbAOMq4bpxTvOVz33LIehLe3fu6NwqC9tdOcxJXEhyZ4simqXTC") .roles("USER"); }
@Configuration @Order(1) public static class AdminSecurityConfig extends WebSecurityConfigurerAdapter{ @Override protected void configure(HttpSecurity http) throws Exception { http.antMatcher("/admin/**").authorizeRequests() //該類配置url為/admin/ .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(); } } }
@Service public class MethodService { @Secured("ROLE_ADMIN") //訪問此方法需要ADMIN角色 public String admin() { return "hello admin"; } @PreAuthorize("hasRole('ADMIN') and hasRole('DBA')") //訪問此方法需要ADMIN且DBA public String dba() { return "hello dba"; } @PreAuthorize("hasAnyRole('ADMIN','DBA','USER')") //三個都行 public String user() { return "user"; } }