在 Java 开发领域常见的 安全框架有 Shiro 和 Spring Security 。 Shiro 是一个轻量级的安全管理框架,提供了认证、授权、会话管理、密码管理、缓存管理等功能, Spring Security 是一个相对复杂的安全管理框架,功能比Shiro 更加强大,权限控制细粒度更高 ,对 OAuth 2 的 支持也 更友好,又因为 Spring Security 源自Spring 家族,因 此可以和 Spring 框架无缝整合,特别是 Spring Boot 中提供的自动化配置方案,可以让 Spring Security 的使用 更加便捷。
Spring Security
引入依赖
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
测试
编写一个控制器
/**
*
*
* @Auther: MaWenyi
* @Date: 2019/6/30
* @Description: work.idler.dtai.controller
* @version: 1.0
*/
@Api(value = "测试")
@RestController
public class HelloController {
@ApiOperation(value = "hello请求", notes = "无参")
@GetMapping("/hello")
public String hello() {
return "Hello";
}
}
访问
/hello
接口时Spring Security
就会拦截, 需要登录才能访问。
配置登录用户名和密码
# security 配置
security:
user:
name: admin
password: xxx
roles: admin
基于内存的配置
MyWebSecurityConfig.java
/**
* Spring Security 配置
*
* @author iscolt
* @date 2019/07/08
*/
@Configuration
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().ignoringAntMatchers("/druid/*");
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 添加两个用户
auth.inMemoryAuthentication()
.withUser("admin").password("xxxx").roles("ADMIN","USER")
.and()
.withUser("iscolt").password("xxxx").roles("USER");
}
}
HttpSecurity
权限管理
/**
* Spring Security 配置
*
* @author iscolt
* @date 2019/07/08
*/
@Configuration
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 不拦截druid
http.csrf().ignoringAntMatchers("/druid/*");
// HttpSecurity 配置
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").access("hasAnyRole('ADMIN','USER')") // 或
.antMatchers("/db/**").access("hasRole('DBA') and hasRole('ADMIN')") // 与
.antMatchers("/").permitAll()
.and()
.formLogin()
.loginProcessingUrl("/admin/login").permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("root").password("xxx.").roles("ADMIN", "USER", "DBA")
.and()
.withUser("admin").password("xxx").roles("ADMIN", "USER")
.and()
.withUser("user").password("xxx").roles("USER");
}
}
说明:
> 1. 配置了三个用户
> 2. 访问admin
相关需要有ADMIN
权限
> 3. 访问user
相关需要ADMIN
或USER
权限
> 4. 访问db
相关需要DBA
和ADMIN
权限
> 5. 放行登录
> 6. 放行/
, 除上面配置以外
登录表达详细配置(用于分离)
在前面基础上增加配置
.and()
.formLogin()
.loginPage("/admin_login")
.loginProcessingUrl("/admin/login")
.usernameParameter("username")
.passwordParameter("password")
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Authentication authentication)
throws IOException, ServletException {
Object principal = authentication.getPrincipal();
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
httpServletResponse.setStatus(200);
Map<String, Object> map = new HashMap<>();
map.put("status", 200);
map.put("msg", principal);
ObjectMapper objectMapper = new ObjectMapper();
out.write((objectMapper.writeValueAsString(map)));
out.flush();
out.close();
}
})
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AuthenticationException e)
throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
httpServletResponse.setStatus(401);
Map<String, Object> map = new HashMap<>();
map.put("status", 401);
if (e instanceof LockedException) {
map.put("msg", "账户被锁定,登录失败!");
} else if (e instanceof BadCredentialsException) {
map.put("msg", "账户名或密码输入错误,登录失败!");
} else if (e instanceof DisabledException) {
map.put("msg", "账户被警用,登录失败!");
} else if (e instanceof AccountExpiredException) {
map.put("msg", "账户已过期,登录失败!");
} else if (e instanceof CredentialsExpiredException) {
map.put("msg", "密码已过期,登录失败!");
} else {
map.put("msg", "登录失败!");
}
ObjectMapper objectMapper = new ObjectMapper();
out.write(objectMapper.writeValueAsString(map));
out.flush();
out.close();
}
})
.permitAll();
说明:
> 1. 配置登录页面
> 2. 配置登录url
> 3. 配置用户密码属性名
> 4. 配置成功返回的json
> 5. 配置失败返回的json
postman 等工具测试
注销登录配置
在上面基础添加配置
.and()
.logout().logoutUrl("/admin/logout")
.clearAuthentication(true)
.invalidateHttpSession(true)
.addLogoutHandler(new LogoutHandler() {
@Override
public void logout(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Authentication authentication) {
}
})
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Authentication authentication)
throws IOException, ServletException {
httpServletResponse.sendRedirect("/admin_login");
}
});
说明:
- 配置退出登录借口路径
- 清除session
- 退出成功反应
多个 HttpSecurity
/**
* 多个HttpSecurity配置, 用于业务复杂的情况
*
* @author iscolt
* @date 2019/07/11
*/
@Configuration
public class MultiHttpSecurityConfig {
@Bean
PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
@Autowired
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 添加两个用户
auth.inMemoryAuthentication()
.withUser("root").password("mwy594211.").roles("ADMIN", "USER", "DBA")
.and()
.withUser("admin").password("594211").roles("ADMIN", "USER")
.and()
.withUser("user").password("5211").roles("USER");
}
@Configuration
@Order(1)
public static class AdminSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
// TODO 进行其他配置
}
}
@Configuration
public static class OtherSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
// TODO 进行其他配置
}
}
}
配置多个
HttpSecurity
时, 配置类不需要继承WebSecurityConfigurerAdapter
,
在配置类中创建静态内部内继承WebSecurityConfigurerAdapter
即可
注意:
> 1、 @Order配置的是优先级,数字越小, 优先级越高, 未加的优先级最小
密码加密
配置文件中添加配置
@Bean
PasswordEncoder passwordEncoder() {
// return NoOpPasswordEncoder.getInstance();
/**
* 密钥迭代次数(密码加密)
*
* 注册,添加用户的时候需要BCryptPasswordEncoder加密
*/
return new BCryptPasswordEncoder(10);
}
方法安全
在配置类上添加
/**
* prePostEnabled = true 会解锁@PreAuthorize 和 @PostAuthorize 两个注解
* @PreAuthorize 在方法前验证
* @PostAuthorize 在方法后验证
* @PreAuthorize 和 @PostAuthorize 中都可以使用基于表达式的语法
* securedEnabled = true 会解锁 @Secured 注解
* @Secured("ROLE_ADMIN") 表示访问该方法需要 ADMIN 权限
*/
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
新建TestService
/**
* 测试业务逻辑层
*
* @author iscolt
* @date 2019/07/11
*/
@Service
public class TestService {
@Secured("ROLE_ADMIN")
public String admin() {
return "hello admin";
}
@PreAuthorize("hasRole('ADMIN') and hasRole('DBA')")
public String dba() {
return "hello dba";
}
@PreAuthorize("hasAnyRole('ADMIN', 'DBA', 'USER')")
public String user() {
return "You are so ordinary";
}
}
基于数据库认证
参考文献..