前言
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页面,提示没有权限,这说明我们的配置是正确的。