前言
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頁面,提示沒有權限,這說明我們的配置是正確的。