Spring Security是一个强大的、高度可定制的认证和访问控制框架,它专注于为Java应用程序提供身份验证和授权。像所有Spring项目一样,Spring Security的真正强大之处在于它可以很容易地扩展以满足定制需求。
功能特点:
- 对身份验证和授权的全面和可扩展的支持
- 防范诸如会话固定、点击劫持、跨站点请求伪造等攻击
- Servlet API集成
- 与Spring Web MVC的可选集成
SpringSecurity的详细使用
首先引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
设置登陆的用户名和密码的四种方式:前三种方式仅做了解,可以跳过
方式一:基于配置文件方式
方式二和方式三:基于重写(自定义)配置类方式
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfigure extends WebSecurityConfigurerAdapter {
// 方式二:配置类继承WebSecurityConfigurerAdapter并重写方法
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
String encode = passwordEncoder().encode("123"); // 将密码加密
// 定义账号,密码,权限
auth.inMemoryAuthentication().withUser("lucy").password(encode).roles("admin");
}
// 修改认证参数方式三:方式一和方式二都是一样的写死的,我们应该从数据库获取账号密码,
// 要自定义UserDetailsService,具体使用在框架搭建中有详细说明
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
// 这个要有,框架要对密码做加密,不能是明文。
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder(); // security框架自带的加密工具
}
}
框架搭建步骤和使用详解:
WebSecurityConfigurerAdapter:Security框架的核心配置类
UserDetailsService:查询数据库中用户名和密码,并返回
UserDetails:依赖于GrantedAuthority(权限),用于保存用户信息(账号、密码、角色、权限),并且有框架默认实现类 org.springframework.security.core.userdetails.User;
GrantedAuthority:装载单个权限,多个权限需要多个GrantedAuthority对象装载
步骤一: 自定义UserDetails接口实现类
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
// UserDetails:保存从数据库中获取的账号密码、聚合了GrantedAuthority(权限)
public class Account implements UserDetails {
private String username;
private String password;
private Set<? extends GrantedAuthority> authorities = new HashSet<>();
public Account() {}
public Account(String username, String password, Set<? extends GrantedAuthority> authorities) {
this.username = username;
this.password = password;
this.authorities = authorities;
}
// 返回权限集合
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
// 当前账户是否过期
@Override
public boolean isAccountNonExpired() {
return true;
}
// 当前账户是否未锁定
@Override
public boolean isAccountNonLocked() {
return true;
}
// 当前账户密码是否未过期
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// 当前账户是否可用
@Override
public boolean isEnabled() {
return true;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setAuthorities(Set<? extends GrantedAuthority> authorities) {
this.authorities = authorities;
}
}
步骤二:自定义GrantedAuthority接口实现类
import org.springframework.security.core.GrantedAuthority;
// Permission 装载授权信息
public class Permission implements GrantedAuthority {
private String value;
public Permission() {}
@Override
public String getAuthority() {
return this.value;
}
public void setValue(String value) {
this.value = value;
}
}
步骤三:自定义UserDetailsService接口实现类
import org.springframework.security.core.userdetails.UserDetailsService;
@Service
public class AccountDetailsService implements UserDetailsService {
// 引入数据库操作Api,数据从数据库获取
@Resource
AccountDao accountDao;
// loadUserByUsername接收账号,
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 从数据库中查找用户数据以及对应权限,封装到Account中并返回,最终框架帮我们完成认证和授权
// 为了方便理解这里写死了
Account account = new Account();
account.setUsername("123456@qq.com");
account.setPassword("123456789");
// 多个权限
Permission permission1 = new Permission();
permission1.setValue("qx:create_user"); // 此处字符串随意,这里只是命名规范
Permission permission2 = new Permission();
permission2.setValue("qx:updates_user");
// 将多个权限装载到一个集合中,List也可以
Set<Permission> set = new HashSet<>();
set.add(permission1);
set.add(permission2);
// 将集合封装到account中,然后返回account
account.setAuthorities(set);
return account;
}
}
步骤四:自定义WebSecurityConfigurerAdapter
package com.example.demo3.security;
// @Configuration有了以下任何一个注解都可以不用
// @EnableWebSecurity这个表示启用Web安全的注解,如果你已经是是一个web 项目,不需要使用此注解,
// Springboot的自动配置机制WebSecurityEnablerConfiguration已经引入了该注解
//@EnableGlobalMethodSecurity开启这个来判断用户对某个控制层的方法是否具有访问权限(见Controller层的@PreAuthorize)
// 这个注解很重要,如果没有这个注解,那么Controller里的方法将不受约束,只要登录成功就能访问,而无视是否有改权限。
// securedEnabled 开启@Secured注解扫描
// prePostEnabled 开启@PrePostEnabled注解扫描
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class SecurityConfigure extends WebSecurityConfigurerAdapter {
// 修改认证参数方式四:会用到
@Resource
private UserDetailsService userDetailsService;
// 核心配置方法
@Override
protected void configure(HttpSecurity http) throws Exception {
// 核心部分(以下配置的顺序不能随意):
// 配置控制器URL的角色和权限
http.authorizeRequests()
.antMatchers("login","/loginout","/upload").permitAll() // 放行路径
.anyRequest().authenticated() // 表示除了放行路径,其它路径都要认证授权
/*了解部分,后面用注解替代
// 设置访问权限:
// 只有具有admin权限才可以访问这个路径(Controller)
.antMatchers("/handler1").hasAuthority("admin")
// 只要包含admin1 权限就可以访问这个路径
.antMatchers("/handler2").hasAnyAuthority(new String[]{"admin1","admin2"})
// 必须同时具备多个 权限才可以访问这个路径
.antMatchers().access("hasAuthority('qx1') and hasAuthority('qx2')")
// 设置访问角色:
// 只有具有给定角色才可以访问这个路径
.antMatchers("/handler1").hasRole("role1")
// 只要包含admin权限就可以访问这个路径
.antMatchers("/handler2").hasAnyRole("role1","role2")
// 同时具备多个角色才可以访问这个路径
.antMatchers().access("hasRole('role1') and hasRole('role2') and hasRole('role3')")
*/
.and()
.formLogin() // 访问非放行路径都会弹出表单(重定向到登陆页面)
.and()
// 修改认证参数方式四:
// 这一步,告诉Security 框架,我们要用自己的UserDetailsService实现类
// 来传递UserDetails对象给框架,框架会把这些信息生成Authorization对象使用
.userDetailsService(userDetailsService);
/*了解部分
// 登陆相关URL自定义
http.formLogin()
.loginPage("/login.html").permitAll() // 自定义的登陆页面路径
.usernameParameter("username").passwordParameter("password") // 账号密码
.loginProcessingUrl("/user/login") // 登陆访问路径
.defaultSuccessUrl("/test/index").permitAll() // 登陆成功后,跳转路径
.failureUrl("/userLogin?error"); // 登陆失败跳转路径
// 退出登陆url和退出登陆成功后跳转url
http.logout()
.logoutUrl("/logout")
.clearAuthentication(true) // 清除身份认证信息
.invalidateHttpSession(true) // 使session失效
.logoutSuccessUrl("/index").permitAll();
// 异常跳转页面
http.exceptionHandling().accessDeniedPage("/error.html");
*/
//关闭跨域和csrf保护,框架默认开启
http.cors().disable()
.csrf().disable();
}
// 这个要有,框架要对密码做加密,不能是明文,这里用的是框架自带的BCrypt加密器
// 也可以自定义加密方式实现PasswordEncoder接口再return自定义实现类
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
步骤五:Controller层各种注解的用法:
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.access.prepost.PreFilter;
@RestController
@RequestMapping("/test")
public class UserHandler {
private static Logger logger = LoggerFactory.getLogger(UserHandler.class);
@RequestMapping("/t1")
// 用户包含 某个 角色才能访问此方法
@Secured({"ROLE_sale","ROLE_manager"})
public String t1(){
logger.info("成功访问t1");
return "成功访问t1";
}
// 两个注解的区别:
// @PreAuthorize:方法执行前进行权限验证
// @PostAuthorize:方法返回后才进行权限验证
// 注意t2和t3区别:
// hasAuthority:用户同时拥有一个或多个权限才能访问此方法
// hasAnyAuthority:用户包含 某个 权限才能访问此方法
// 多个权限之间用 , 号隔开
@RequestMapping("/t2")
@PostAuthorize("hasAuthority('admins:admin1')")
//@PostAuthorize("hasAnyAuthority('admin','boss')")
public String t2(){
logger.info("成功访问t2");
return "成功访问t2";
}
@RequestMapping("/t3")
@PreAuthorize("hasAuthority('admins:admin1')")
//@PreAuthorize("hasAuthority('admin' and 'boss')")
//@PreAuthorize("hasAnyAuthority('admin','boss')")
public String t3(){
logger.info("成功访问t3");
return "成功访问t3";
}
// 注意t4和t5区别:
// hasRole:用户必须同时拥有一个或多个角色才能访问此方法
// hasAnyRole:用户包含 某个 角色才能访问此方法
// 多个角色之间用 , 号隔开
@RequestMapping("/t4")
@PreAuthorize("hasRole('teacher')")
// @PreAuthorize("hasRole('teacher' and 'boss')")
//@PreAuthorize("hasAnyRole('teacher','role1','role2')")
public String t4(){
logger.info("成功访问t4");
return "成功访问t4";
}
@RequestMapping("/t5")
@PostAuthorize("hasRole('teacher')")
// @PostAuthorize("hasRole('teacher' and 'boss')")
//@PostAuthorize("hasAnyRole('teacher','role1','role2')")
public String t5(){
logger.info("成功访问t5");
return "成功访问t5";
}
// 两个注解的区别:
//@PreFilter:方法执行前处理
//@PostFilter:方法返回后处理
@RequestMapping("/6")
// filterObject.name=='老一':表示只传入集合中 每个对象的sex属性值 == '男' 的所有对象。
@PreFilter("filterObject.sex=='男'") // 前置过滤器
public String t6(@RequestBody List<User> userList){
userList.forEach(t -> {
System.out.println(t.getName()+t.getSex()+t.getAge());
});
logger.info("成功访问t6");
return "成功访问t6";
}
@RequestMapping("/t7")
// filterObject.name=='老一':表示只返回集合中 每个对象的name属性值 == '老一' 的所有对象。
// 返回结果:User user1 = new User("老一", "男", 20);
@PostFilter("filterObject.name=='老一'") // 后置过滤器,方法返回后处理
public List<User> t7(){
logger.info("成功访问t7");
User user1 = new User("老一", "男", 20);
User user2 = new User("老二", "男", 20);
ArrayList<User> list = new ArrayList<>();
list.add(user1);
list.add(user2);
return list;
}
}
以上步骤完成可以访问在浏览器访问/login,会出现框架返回的登陆页面,然后进行各种测试。
上面Controller中注解使用细节:
1.要让其中的注解被扫描到还需要到SecurityConfig中开启注解扫描配置如下:
2.Role(角色)命名最终会加上前缀 “ ROLE_ ” ,所以我们数据库的命名也要加上前缀, 具体看下图源码:
官网链接:Spring Security