精通 Spring Boot 系列文(12)
阅读全文,约 18 分钟
Spring Boot 的安全管理
1、Spring Security 是啥?
Spring Security 是 Spring 的一个安全模块,它很强大,但使用特别复杂。在安全管理这个领域,之前还有一个 Shiro 是比较受欢迎的,对于大部分的应用,Shiro 用得也比较成熟。Spring Boot 现在为 Spring Security 提供了自动化配置方案,用起来非常方便,所以大家慢慢就选择使用了 Spring Security 了。
最近,很多安全管理技术栈的组合长这样的:Spring Boot/Spring Cloud + Spring Security。
安全框架有两大主要操作:认证(Authentication)和授权(Authorization)。
2、Spring Security 简单使用
如何配置 Spring Security?非常简单,我们直接在类上继承 WebSecurityConfigurerAdapter 适配器即可,然后再用 @EnableWebSecurity 注解,再重写
configure() 方法来配置对应的安全信息。
我们还需要了解两个事情:用户认证、用户授权
2.1 用户认证
主要通过在
configureGlobal(AuthenticationManagerBuilder amb) 方法完成用户认证,然后通过
AuthenticationManagerBuilder 的 inMemoryAuthentication() 方法来添加用户,和用户的权限。
2.2 用户授权
主要通过 configure(HttpSecurity hs) 方法,完成用户授权,然后 HttpSecurity 的 authorizeRequests() 方法能设置多个 macher 节点来声明执行顺序,这样用户就能够访问多个 URL 模式了。
当你匹配了对应的请求路径之后,然后再执行安全处理。
Spring Security 的安全处理方法:
anyRequest:匹配所有路径
access:可以访问,当 Spring EL 的结果为 ture
anonymous:匿名可访问
denyAll:用户不能访问
fullyAuthenticated:用户完全认证可访问
hasAnyAuthority:参数代表权限,列出来任何一个的可访问
hasAnyRole:参数代表角色,列出来任何一个的可访问
hasAuthority:参数代表权限,列出来的可访问
hasIpAddress:参数代表 IP 地址,匹配的可访问
hasRole:参数角色,列出来的可访问
permitAll:用户可以任意访问
rememberMe:允许通过 remember-me 登录的用户访问
authenticated:用户登录后可访问
3、Spring Security 简单案例
1)编辑 pom.xml 文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.nxgroupId>
<artifactId>springbootdataartifactId>
<version>1.0-SNAPSHOTversion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.6.RELEASEversion>
<relativePath/>
parent>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<scope>testscope>
dependency>
dependencies>
project>
2)创建 NXPasswordEncoder 认证逻辑类
public class NXPasswordEncoder implements PasswordEncoder{
@Override
public String encode(CharSequence arg0) {
return arg0.toString();
}
@Override
public boolean matches(CharSequence arg0, String arg1) {
return arg1.equals(arg0.toString());
}
}
3)创建 AppSecurityConfigurer 密码器
目前,Spring Security 的密码存储格式为:{id}encodedPassword,其中 id 是用来找到对应的 PasswordEncoder,encodedPassword 用来指原始密码经过加密之后的密码。当我们想自定义密码器,必须实现 PasswordEncoder 接口。
@Configuration
public class AppSecurityConfigurer extends WebSecurityConfigurerAdapter{
// 注入认证处理类,处理不同用户跳转到不同的页面
@Autowired
AppAuthenticationSuccessHandler appAuthenticationSuccessHandler;
// 用户授权操作
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 需要过滤静态资源
.antMatchers("/login","/css/**","/js/**","/img/*").permitAll()
.antMatchers("/", "/home").hasRole("USER")
.antMatchers("/admin/**").hasAnyRole("ADMIN", "DBA")
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login").successHandler(appAuthenticationSuccessHandler)
.usernameParameter("loginName").passwordParameter("password")
.and()
.logout().permitAll()
.and()
.exceptionHandling().accessDeniedPage("/accessDenied");
}
// 用户认证操作
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// 需要密码编码器
auth.inMemoryAuthentication().passwordEncoder(new NXPasswordEncoder()).withUser("nx").password("888888").roles("USER");
auth.inMemoryAuthentication().passwordEncoder(new NXPasswordEncoder()).withUser("admin").password("admin").roles("ADMIN","DBA");
}
}
4)创建 AppAuthenticationSuccessHandler 认证成功处理类
@Component
public class AppAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler{
// 通过 RedirectStrategy 对象负责所有重定向事务
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
// 重写 handle 方法,通过 RedirectStrategy 对象重定向到指定的 url
@Override
protected void handle(HttpServletRequest request, HttpServletResponse response,
Authentication authentication)
throws IOException {
// 通过 determineTargetUrl 方法返回需要跳转的 url
String targetUrl = determineTargetUrl(authentication);
redirectStrategy.sendRedirect(request, response, targetUrl);
}
// 从 Authentication 对象中提取角色提取当前登录用户的角色,并根据其角色返回适当的 URL。
protected String determineTargetUrl(Authentication authentication) {
String url = "";
// 获取当前登录用户的角色权限集合
Collection extends GrantedAuthority> authorities = authentication.getAuthorities();
List roles = new ArrayList();
for (GrantedAuthority a : authorities) {
roles.add(a.getAuthority());
}
// 判断不同角色跳转到不同的url
if (isAdmin(roles)) {
url = "/admin";
} else if (isUser(roles)) {
url = "/home";
} else {
url = "/accessDenied";
}
System.out.println("url = " + url);
return url;
}
private boolean isUser(List roles) {
if (roles.contains("ROLE_USER")) {
return true;
}
return false;
}
private boolean isAdmin(List roles) {
if (roles.contains("ROLE_ADMIN")) {
return true;
}
return false;
}
public void setRedirectStrategy(RedirectStrategy redirectStrategy) {
this.redirectStrategy = redirectStrategy;
}
protected RedirectStrategy getRedirectStrategy() {
return redirectStrategy;
}
}
5)创建 NXController 控制器
@Controller
public class NXController {
@RequestMapping("/")
public String index() {
return "index";
}
@RequestMapping(value = "/login")
public String login() {
return "login";
}
@RequestMapping("/home")
public String homePage(Model model) {
model.addAttribute("user", getUsername());
model.addAttribute("role", getAuthority());
return "home";
}
@RequestMapping(value = "/admin")
public String adminPage(Model model) {
model.addAttribute("user", getUsername());
model.addAttribute("role", getAuthority());
return "admin";
}
@RequestMapping(value = "/dba")
public String dbaPage(Model model) {
model.addAttribute("user", getUsername());
model.addAttribute("role", getAuthority());
return "dba";
}
@RequestMapping(value = "/accessDenied")
public String accessDeniedPage(Model model) {
model.addAttribute("user", getUsername());
model.addAttribute("role", getAuthority());
return "accessDenied";
}
@RequestMapping(value="/logout")
public String logoutPage (HttpServletRequest request, HttpServletResponse response) {
// Authentication是一个接口,表示用户认证信息
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
// 如果用户认知信息不为空,注销
if (auth != null){
new SecurityContextLogoutHandler().logout(request, response, auth);
}
// 重定向到login页面
return "redirect:/login?logout";
}
private String getUsername(){
// 从SecurityContex中获得Authentication对象代表当前用户的信息
String username = SecurityContextHolder.getContext().getAuthentication().getName();
System.out.println("username = " + username);
return username;
}
private String getAuthority(){
// 获得Authentication对象,表示用户认证信息。
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
List roles = new ArrayList();
for (GrantedAuthority a : authentication.getAuthorities()) {
roles.add(a.getAuthority());
}
System.out.println("role = " + roles);
return roles.toString();
}
}
最后,大家可以找一套前端页面,测试一下即可,非常简单的。
Java后端编程
更多Java推文,关注公众号