只要我們引入spring-boot-starter-security:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
就會獲得一個免費的login page,username為user,password會在Spring啟動的時候從console得知。
不過我們並不一定想要一進入webapp就遇到登入頁面,這時候就得需要自己設定其他security相關的東西了。
Spring也提供了password encoder的工具,這個機制可以很方便讓user輸入的密碼經過特定演算法加密,而我們資料庫所儲存的都是加密過後的密碼,而user要登入時SpringApp所進行的驗證也都透過加密過的字串來比對。
package tacos.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
public class SomeClassEnvolveUser{
@Autowired
public PasswordEncoder pwdEncoder;
public User createUser(String username, String password){
User user = new User(username, encoder.encode(password),
Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")));
return user;
}
}
身為Java developer,要儲存User資訊,首先肯定會想到來建立了User類別。User類別可以implements了Spring Security的UserDetails,會預先定義了isAccountNonExpired, isEnabled()等等的方法介面,提醒開發可以實作這些方法,方便日後的security相關使用。
再來就是UserDetailService, UserRepository的建立,以及建立一個Thymeleaf的page,這邊也可建立一個類似dto概念的RegistrationForm,並實作一個toUser的方法轉換為User。
有了客製化的user註冊登入機制,再來就是控制哪些地方需要登入,或者哪些功能需要哪些權限。我們可以透過SecurityFilterChain(HttpSecurity http)的Bean簡單做到這件事情:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeRequests()
.antMatchers("/design", "/orders").hasRole("USER")
.antMatchers("/", "/**").permitAll()
.and()
.build();
}
可以看到我們針對不同的路徑決定所需要的權限,或者permitAll,不須權限皆可訪問。而以上可透過access()方法搭配SpEL來有更彈性的寫法:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeRequests()
.antMatchers("/design", "/orders").access("hasRole('USER')")
.antMatchers("/", "/**").access("permitAll()")
.and()
.build();
}
除了自行定義的帳號密碼登入,也可透過OAuth2來達到同樣的目的。引入spring-boot-starter-oauth2-client即可,並在application.properties進行設定。
spring:
security:
oauth2:
client:
registration:
facebook:
clientId: <facebook client id>
clientSecret: <facebook client secret>
scope: email, public_profile
Spring Security預設就會為表單附上CSRF(Cross-site request forgery)的防護,這種防護通常是在表單多加上一個隱藏的CSRF token,這個token只有發出表單的server知道,所以當表單被其他程式過一手後,他們不會知道這個token是多少,或者是如何被辨認,就可知道表單有沒有被過一手汙染。
除了上述的防護外,Spring Security也支援method-level的設定,通常就是透過@PreAuthorize(”hasRole(’ADMIN’)”)的annotation來達成,而要能使用@PreAuthorize,就得在SecurityConfig的@Configuration上貼上@EnableGlobalMethodSecurity:
@Configuration
@EnableGlobalMethodSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
}
最後,我們會希望在例如訂單等紀錄上可以紀上是哪個user的,Spring Security也讓我們可以很方便去取得一個authenticated的user instance來去應用,可透過以下幾種方式:
@PostMapping
public String processOrder(@Valid TacoOrder order, Errors errors,
SessionStatus sessionStatus,
Principal principal) {
...
User user = userRepository.findByUsername(
principal.getName());
order.setUser(user);
...
}
@PostMapping
public String processOrder(@Valid TacoOrder order, Errors errors,
SessionStatus sessionStatus,
Authentication authentication) {
...
User user = (User) authentication.getPrincipal();
order.setUser(user);
...
}
@PostMapping
public String processOrder(@Valid TacoOrder order, Errors errors,
SessionStatus sessionStatus,
@AuthenticationPrincipal User user) {
if (errors.hasErrors()) {
return "orderForm";
}
order.setUser(user);
orderRepo.save(order);
sessionStatus.setComplete();
return "redirect:/";
}