阅读全文,约 18 分钟
这是江帅帅的第021篇原创
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.0</modelVersion> <groupId>com.nx</groupId> <artifactId>springbootdata</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.6.RELEASE</version> <relativePath/> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <!-- 添加spring-boot-starter-web模块依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 添加spring-boot-starter-thymeleaf模块依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- 添加spring-boot-starter-security 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </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<String> roles = new ArrayList<String>(); 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<String> roles) { if (roles.contains("ROLE_USER")) { return true; } return false; } private boolean isAdmin(List<String> 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<String> roles = new ArrayList<String>(); for (GrantedAuthority a : authentication.getAuthorities()) { roles.add(a.getAuthority()); } System.out.println("role = " + roles); return roles.toString(); } }
最后,大家可以找一套前端页面,测试一下即可,非常简单的。