Spring Security(1)--- 許可權控制基本功能實現
前言
Spring Security
由acegi
進化而來,是一個安全許可權管理框架,功能十分的強大。
但也正是因為功能強大,使用起來就變的非常的麻煩,至少個人感覺很煩很煩,甚至覺得Spring Security
是不是應該為常規的Java web應用出一個簡化版?相對而言Shiro
就清爽很多,當然這裡不討論誰好誰壞,能解決專案的問題就好。
官方給出的示例中(包括網上一搜就找到的一堆資料)是不使用資料庫的,所有的許可權配置都寫死在配置檔案和程式碼中,這在實際專案中顯然是很難滿足的,難道老外的許可權需求真的如此簡單麼?
而想要實現許可權的動態可配,友好的提示資訊等,這些都需要自己去實現,這實現的過程還是很煩鎖的,特別是對Spring Security
目前網上的文章大多都是用xml
配置來實現的,本文將全部使用JavaConfig
的方式,也不會過多的講解Spring Security
的內容,重在使用,能滿足當前專案的需求就好。
下面以一個最簡單的示例開始。
新增依賴
maven專案,第一步依然是新增我們需要的依賴。
在這個示例中,只是簡單的演示,並沒有涉及到資料庫,所以暫時只需要這些。嗯,另外模板引擎換成了thymeleaf
,不再是我以前一直使用的velocity
了,因為我發現thymeleaf
有些地方更好用一些。
<dependencies>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
系統許可權設計
在本示例中,有以下幾個頁面,區分不同的許可權:
- 首頁 所有人都可以訪問
- 登入頁 所有人可都可以訪問
- 歡迎頁 登入後的使用者都可以訪問
- 管理頁 只有管理員可訪問
- 無許可權提醒頁 當一個使用者的訪問沒有許可權時,跳轉到該頁
確定了以上頁面,接下來就是建立相應的使用者了。
建立使用者物件
為了簡單起見,我們的使用者只需要使用者名稱、密碼以及一個對應的角色。
public class User {
private String username;
private String password;
private String role;
}
使用者登入資料層
這裡我們並沒有真正的資料層,只是建立幾個模擬使用者資料:
public class UserDaoImpl implements UserDao {
private static final Map<String, User> userMap = new HashMap<String, User>();
static {
User user = new User();
user.setUsername("liyd");
user.setPassword("123456");
user.setRole("user");
userMap.put(user.getUsername(), user);
user = new User();
user.setUsername("admin");
user.setPassword("123456");
user.setRole("admin");
userMap.put(user.getUsername(), user);
}
@Override
public User getUser(String username) {
return userMap.get(username);
}
}
前端展現
在展現層中,我們需要前面提到的幾個頁面,並增加一個Controller
,程式碼如下:
@Controller
public class UserController {
@RequestMapping(value = { "", "/index" }, method = RequestMethod.GET)
public String home() {
return "index";
}
@RequestMapping(value = "/user-page", method = RequestMethod.GET)
public String userPage() {
return "user-page";
}
@RequestMapping(value = "/admin-page", method = RequestMethod.GET)
public String adminPage() {
return "admin-page";
}
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String login() {
return "login";
}
@RequestMapping("/403")
public String forbidden() {
return "403";
}
}
可以看到都是簡單的跳轉到相應頁面,所有的頁面都在resources/templates
下,這個就不細講了。
從上面可以看出/login
只是做了一個登入頁跳轉,但是具體登入的驗證邏輯卻沒有,這是因為Spring Security
要求使用者將此塊的功能必須委託給它來處理。
另外/403
實際上是使用者訪問沒有許可權時跳轉的頁面,Spring Security
會設定此時的http狀態碼為403,因此我們需要設定一個錯誤頁處理,當發現http狀態碼為403時跳轉到/403
處理。
@Configuration
public class WebAppConf extends WebMvcConfigurerAdapter {
@Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
return new EmbeddedServletContainerCustomizer() {
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
ErrorPage error403Page = new ErrorPage(HttpStatus.FORBIDDEN, "/403");
container.addErrorPages(error403Page);
}
};
}
}
新增許可權驗證
到上面那一步,系統功能已經差不多了,但是還缺少許可權驗證的配置。
其實許可權控制從你向maven的pom.xml中新增spring-boot-starter-security
依賴開始就已經起作用了,
如果這時候你啟動專案訪問的話,會發現Spring Security
已經將所有請求攔截並自動生成了一個登入框讓你登入。
但顯然這個登入框你是無法登入成功的,因為後臺具體登入的邏輯我們還沒有完成。
建立自定義的UserDetailsService
Spring Security
的使用者資訊獲取最終是通過UserDetailsService
的loadUserByUsername
方法來完成的,這個後面會細講,這裡先做了解。
根據上面的UserDao
實現,我們建立自定義的CustomUserDetailService
,至於角色的字首,我記得Spring Security 3.2.x版本是不需要你手動再加的,
這裡我用的是Spring Boot 1.3.3,Spring Security版本為4.0.3,不知道為什麼又要加上了,看AffirmativeBased
裡面的原始碼除錯,確實是一個有字首一個沒字首,搞不懂Spring Security
走的什麼路子。
public class CustomUserDetailsService implements UserDetailsService {
private static Map<String, User> userMap = new HashMap<String, User>();
static {
User user = new User("admin", "123456", "admin");
userMap.put(user.getUsername(), user);
user = new User("selfly", "123456", "user");
userMap.put(user.getUsername(), user);
}
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
User user = userMap.get(s);
if (user == null) {
throw new UsernameNotFoundException("not found");
}
List<SimpleGrantedAuthority> authorities = new ArrayList<SimpleGrantedAuthority>();
authorities.add(new SimpleGrantedAuthority("ROLE_" + user.getRole()));
LOG.info("username:{},role:{}", user.getUsername(), user.getRole());
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(),
authorities);
}
}
配置Security
接下來就是配置Spring Security
了,我們建立一個類SecurityConf
,使用JavaConfig
的方式,指定AuthenticationManager
使用我們自己的CustomUserDetailsService
來獲取使用者資訊,並設定首頁、登入頁等。
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConf extends WebSecurityConfigurerAdapter {
@Bean
public UserDetailsService userDetailsService() {
return new CustomUserDetailsService();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/index")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/user-page")
.permitAll()
.and()
.logout()
.permitAll();
}
}
可以看到SecurityConf
上添加了@EnableWebSecurity
註解用來跟Spring mvc
整合。同時它還繼承了WebSecurityConfigurerAdapter
類用來重寫我們需要的配置。
新增角色許可權驗證
上面已經完成了系統的登入和驗證功能,但並沒有進行許可權的區分,要怎麼樣把普通使用者和管理使用者區分開呢?
很簡單,只需要增加@PreAuthorize
註解即可。修改UserController
,對/user
和/admin
分別添加註解:
@RequestMapping(value = "/user", method = RequestMethod.GET)
@PreAuthorize("hasAnyRole('admin', 'user')")
public String userPage() {
return "user-page";
}
@RequestMapping(value = "/admin", method = RequestMethod.GET)
@PreAuthorize("hasAnyRole('admin')")
public String adminPage() {
return "admin-page";
}
啟動專案
現在,可以啟動專案,訪問http://localhost:8080,根據提示來登入檢查一下許可權是否正確。
當使用admin/123456登入時,所有的頁面都是允許訪問的。
當使用selfly/123456登入時,發現訪問/admin時跳到了/403頁面,提示沒有許可權,這說明我們的配置是正確的。