文章目录
Spring Security
简介
Spring Security是一个能够为基于Spring生态圈,提供安全访问控制解决方案的框架。它提供了一组可以在Spring应用上下文中配置的机制,充分利用了Spring的强大特性,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
使用
引入Spring Security依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
一旦引入此依赖后,不需要任何配置,启动项目后访问链接就会需要账号密码登录。
默认账号是:user
每次启动项目生成的密码都不一样,可在项目启动打印日志(需要级别是INFO或以下)中找到:
配置方式
使用配置文件配置
有了安全配置的属性,即使没有加入 @EnableWebSecurity
,Spring Boot也会根据配置的项自动启动安全机制。
spring:
security:
user:
# 用户名,默认user
name: user
# 用户密码
password: 123456
# 用户角色
#roles:
#filter:
# 过滤器排序
#order:
# 安全过滤器责任链拦截的分发类型
#dispatcher-types:
继承WebSecurityConfigurationAdapter
基于内存的认证
新建MyWebSecurityConfig类,继承自WebSecurityConfigurerAdapter:
@Configuration
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder(){
//不操作密码编码器,方便调试使用
return NoOpPasswordEncoder.getInstance();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//基于内存的用户配置在配置角色时不需要添加“ROLE_”前缀
auth.inMemoryAuthentication().withUser("admin").password("123").roles("Role1", "Role2")
.and()
.withUser("user").password("321").roles("Role1");
}
}
HttpSecurity
根据角色来管理受保护的资源,重构configure方法:
@Configuration
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder(){
//不操作密码编码器,方便调试使用
return NoOpPasswordEncoder.getInstance();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//基于内存的用户配置在配置角色时不需要添加“ROLE_”前缀
auth.inMemoryAuthentication().withUser("admin").password("123").roles("Role1", "Role2")
.and()
.withUser("user").password("321").roles("Role1");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//调用authorizeRequests()方法开启HttpSecurity的配置
http.authorizeRequests()
// 访问 “/invoke1”模式的URL必须具备Role1的角色
.antMatchers("/invoke1")
.hasRole("Role1")
// 访问 “/invoke2”模式的URL必须具备Role1或Role2的角色
.antMatchers("/invoke2")
.access("hasAnyRole('Role1','Role2')")
// 访问 “/invoke3/**”模式的URL必须具备Role1或Role2的角色
.antMatchers("/invoke3/**")
.access("hasRole('Role1') and hasRole('Role2')")
// 表示除了前面定义的URL模式外,用户访问其他的URL都必须认证(登录)后访问
.anyRequest()
.authenticated()
//开启表单登录,即一开始的登录页面
.and()
.formLogin()
//配备登录接口为“/login”,发起一个POST请求进行登录,登录参数中用户名默认为username,密码默认是password
.loginProcessingUrl("/login")
//permitAll:表示和登录相关的接口都不需要认证即可访问
.permitAll()
.and()
//关闭csrf
//跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。
.csrf()
.disable();
}
}
controller测试类:
@RestController
public class TestController {
@GetMapping("invoke")
public String invoke() {
return "success";
}
@GetMapping("invoke1")
public String invoke1() {
return "invoke1";
}
@GetMapping("invoke2")
public String invoke2() {
return "invoke2";
}
@GetMapping("invoke3")
public String invoke3() {
return "invoke3";
}
}
登录表单详细配置
主要配置如下:
//开启表单登录,即一开始的登录页面
.and()
.formLogin()
// 配置登录页面,配置loginPage后,如果用户未获授权就访问一个需要授权才能访问的接口,就会自动跳转到“login_page”
//页面让用户登录,login_page为开发者自定义的页面,不再是默认登录页
.loginPage("/login_page")
// 配备登录接口为“/login”,发起一个POST请求进行登录,登录参数中默认用户名为username,默认密码是password
.loginProcessingUrl("/login")
//定义认证所需的用户名和密码的参数名,默认用户名为username,默认密码是password
.usernameParameter("name")
.passwordParameter("passwd")
//定义登录成功的处理逻辑。用户登录成功后,可跳转到某一个页面,也可返回一段json。Authentication可以获取当前登录用户的信息
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response
, Authentication authentication) throws IOException, ServletException {
//获取当前登录用户的信息
Object principal = authentication.getPrincipal();
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
response.setStatus(200);
Map<String, Object> map = new HashMap<>();
map.put("status", 200);
map.put("msg", principal);
ObjectMapper om = new ObjectMapper();
out.write(om.writeValueAsString(map));
out.flush();
out.close();
}
})
//定义登录失败的处理逻辑。登录失败后,可跳转到某一个页面,也可返回一段json。AuthenticationException参数可以获取登录失败的原因。
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response
, AuthenticationException exception) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
response.setStatus(401);
Map<String, Object> map = new HashMap<>();
map.put("status", 401);
if (exception instanceof LockedException) {
map.put("msg", "账户被锁定,登陆失败");
} else if (exception instanceof BadCredentialsException) {
map.put("msg", "账户名或密码输入错误,登录失败");
} else if (exception instanceof DisabledException) {
map.put("msg", "账户被禁用,登录失败");
} else if (exception instanceof AccountExpiredException) {
map.put("msg", "账户已过期,登录失败");
} else if (exception instanceof CredentialsExpiredException) {
map.put("msg", "密码已过期登录失败");
} else {
map.put("msg", "登陆失败");
}
ObjectMapper om = new ObjectMapper();
out.write(om.writeValueAsString(map));
out.flush();
out.close();
}
})
//permitAll:表示和登录相关的接口都不需要认证即可访问
.permitAll()
.and()
使用postman测试时,需要使用POST方式请求:
注销登录配置
主要配置如下:
.and()
// 开启注销登录的配置
.logout()
// 配置注销登录请求URL为“/logout”,默认值也是“/logout”
.logoutUrl("/logout")
// 是否清除身份认证信息,默认为true,表示清除
.clearAuthentication(true)
// 是否使Session失效,默认为true
.invalidateHttpSession(true)
// 配置一个LogoutHandler,开发者可以在LogoutHandler中完成一些数据清除工作。
.addLogoutHandler((request, response, authentication) -> {
})
//配置一个LogoutSuccessHandler,可以在这里处理注销成功后的业务逻辑,例如:返回json或跳转页面
.logoutSuccessHandler((request, response, authentication) -> {
response.sendRedirect("/login_page");
})
LogoutHandler更多实现:
在IDEA中由接口查看实现类图:快捷键 crtl + H 查看hierarchy,只能查看向上向下继承关系,而不能看实现了哪些接口。右键选择Diagrams(也可以使用快捷键ctrl+alt+u,更快捷), crtl + alt + B会显示出跟这个接口有关系的类,然后 全选+enter 即可展示全部类图。
多个HttpSecurity
如果业务比较复杂,也可以配置多个HttpSecurity,实现WebSecurityConfigurerAdapter的多次扩展。
/**
* 配置多个HttpSecurity时,MultiHttpSecurityConfig不需要继承WebSecurityConfigurerAdapter,
* 在MultiHttpSecurityConfig中创建静态内部类继承WebSecurityConfigurerAdapter即可,静态内部类上
* 添加@configuration注解和@Order(表示该配置的优先级,数字越小优先级越大,为加@Order的优先级最小)注解
*/
@Configuration
public class MultiHttpSecurityConfig {
@Bean
PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
@Autowired
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("admin").password("123").roles("Role1", "Role2")
.and()
.withUser("user").password("123").roles("Role1");
}
/**
* 主要用来处理"invoke/**"模式的URL
*/
@Configuration
@Order(1)
public static class AdminSecurityConfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("invoke/**").authorizeRequests().anyRequest().hasRole("Role1");
}
}
/**
* 用于处理其他的Url
*/
@Configuration
public static class OtherSecurityConfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginProcessingUrl("/login")
.permitAll()
.and()
.csrf()
.disable();
}
}
}
配置多个HttpSecurity时,MultiHttpSecurityConfig不需要继承WebSecurityConfigurerAdapter,在MultiHttpSecurityConfig中创建静态内部类继承WebSecurityConfigurerAdapter即可,静态内部类上添加@configuration注解和@Order(表示该配置的优先级,数字越小优先级越大,为加@Order的优先级最小)注解
密码加密
Spring Security提供了多种密码加密方案,官方推荐使用BCryptPasswordEncoder。BCryptPasswordEncoder使用BCrypt强哈希函数。使用时可选择提供strength和SecureRandom实例。strength越大,密钥的迭代次数越多,迭代次数为2^strength,strength的取值为4~31之间,默认10(即1024次)。
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder(10);
}
- 加密密码:
BCryptPasswordEncoder bCryptPasswordEncoder=new BCryptPasswordEncoder(10);
String password = bCryptPasswordEncoder.encode("password");
方法安全
除了基于URL认证与授权,也可以通过注解来灵活地配置方法安全
- 通过
@EnableGlobalMethodSecurity
注解开启基于注解的安全配置:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
public class WebSecurityConfig {
}
- prePostEnabled = true 会解锁
@PreAuthorize
和@PostAuthorize
两个注解。@PreAuthorize会在方法执行前进行验证,@PostAuthorize会在方法执行后进行验证。 - securedEnabled = true 会解锁
@Secured
注解
- 开启注解安全配置后,新建一个MethodService类进行测试:
@Service
public class MethodService {
/**
* 注解@Secured("ROLE_ADMIN")表示访问该方法需要ADMIN角色,需要在角色前加一个前缀"ROLE_"
* @return
*/
@Secured("ROLE_ADMIN")
public String admin() {
return "hello admin";
}
@PreAuthorize("hasRole('ADMIN') and hasRole('DBA')")
public String dba() {
return "hello dba";
}
@PostAuthorize("hasAnyRole('ADMIN','DBA')")
public String user() {
return "hello user";
}
}
- 注解@Secured(“ROLE_ADMIN”)表示访问该方法需要ADMIN角色,需要在角色前加一个前缀"ROLE_"。
测试时,需要使用post登录测试。