【Spring-Security】Re05 权限控制及403处理
一、访问控制方法及控制项:
上述配置中的URL后面都离不开的一个访问控制抉择:
1、全部允许 PermiAll
2、全部拒绝 DenyAll
3、允许匿名访问 Anonymous 也就是普通访问者
4、允许认证之后访问 Authenticated
5、必须完全认证? FullAuthenticated
6、记住我 RememberMe
二、Security权限控制对资源的访问:
之前的权限配置中我们有设置一个权限的角色属性:
package cn.zeal4j.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; /** * @author Administrator * @file IntelliJ IDEA Spring-Security-Tutorial * @create 2020 09 27 21:57 */ @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private PasswordEncoder passwordEncoder; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 1、通过提供的用户名参数访问数据库,查询记录返回过来,如果记录不存在则抛出异常 // username = "admin"; if (!"admin".equals(username)) throw new UsernameNotFoundException("用户名不存在"); // 2、查询出来的凭证是被加密了的,这里是模拟查询的密码 String encode = passwordEncoder.encode("123456"); // 权限不可以为空,所以需要这么一个工具方法简单实现 return new User(username, encode, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal")); } }
现在我们设置两个特定的资源页面:
页面的内容区别就是这个:
<h3>Only admin Allowed</h3>
<h3>Only vip-01 Allowed</h3>
控制器处理:
package cn.zeal4j.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; /** * @author Administrator * @file Spring-Security-Tutorial * @create 2020 09 28 13:59 */ @Controller public class PermitController { @RequestMapping("admin.page") public String toAdminPage() { return "permit/admin"; } @RequestMapping("vip-01.page") public String toVip01Page() { return "permit/vip-01"; } }
现在设置访问权限:
通过hasAuthority方法进行鉴权,具备资质才会允许访问
package cn.zeal4j.configuration; import cn.zeal4j.handler.FarsAuthenticationFailureHandler; import cn.zeal4j.handler.FarsAuthenticationSuccessHandler; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; /** * @author Administrator * @file IntelliJ IDEA Spring-Security-Tutorial * @create 2020 09 27 21:55 */ @Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder getPasswordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity.formLogin(). // 设置登陆行为方式为表单登陆 // 登陆请求参数设置 usernameParameter("username"). passwordParameter("password"). loginPage("/login.html"). // 设置登陆页面URL路径 loginProcessingUrl("/login.action"). // 设置表单提交URL路径 // successForwardUrl("/main.page"). // 设置认证成功跳转URL路径 POST请求 successHandler(new FarsAuthenticationSuccessHandler("https://www.acfun.cn/")). // 使用自定义的重定向登陆 // failureForwardUrl("/error.page"); // 设置认证失败跳转URL路径 POST请求 failureHandler(new FarsAuthenticationFailureHandler("/error.html")); // 跨域处理,不需要跳转了 httpSecurity.authorizeRequests(). regexMatchers(HttpMethod.POST, "正则表达式").permitAll(). // 还可以对符合正则表达式的请求方式进行要求,这个属性使用来制定请求的方式 antMatchers("/**/*.js", "/**/*.css", "/**/images/*.*").permitAll(). // 静态资源放行 antMatchers("/login.html").permitAll(). // 登陆页面允许任意访问 antMatchers("/error.html").permitAll(). // 失败跳转后重定向的页面也需要被允许访问 antMatchers("/admin.page").hasAnyAuthority("admin"). antMatchers("/vip-01.page").hasAnyAuthority("vip-01"). // mvcMatchers("/main.page").servletPath("/xxx").permitAll(). // mvcMatchers资源放行匹配 // antMatchers("/xxx/main.page").permitAll(). // 或者多写MSP的前缀 anyRequest().authenticated(); // 其他请求均需要被授权访问 // CSRF攻击拦截关闭 httpSecurity.csrf().disable(); } }
访问测试:
我们默认登录之后赋予的权限中包含admin,所以访问admin.page是没有问题的
但是如果访问vip-01.page,则会报403错误,访问被禁止了
三、角色控制:
上述的权限设置中可以同时设置角色,书写规范是:
ROLE_角色名称
例如这样:
package cn.zeal4j.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; /** * @author Administrator * @file IntelliJ IDEA Spring-Security-Tutorial * @create 2020 09 27 21:57 */ @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private PasswordEncoder passwordEncoder; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 1、通过提供的用户名参数访问数据库,查询记录返回过来,如果记录不存在则抛出异常 // username = "admin"; if (!"admin".equals(username)) throw new UsernameNotFoundException("用户名不存在"); // 2、查询出来的凭证是被加密了的,这里是模拟查询的密码 String encode = passwordEncoder.encode("123456"); // 权限不可以为空,所以需要这么一个工具方法简单实现 return new User(username, encode, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal,ROLE_vip-01")); } }
之前访问不了的VIP-01资源,现在权限控制规则可以这样设置:
package cn.zeal4j.configuration; import cn.zeal4j.handler.FarsAuthenticationFailureHandler; import cn.zeal4j.handler.FarsAuthenticationSuccessHandler; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; /** * @author Administrator * @file IntelliJ IDEA Spring-Security-Tutorial * @create 2020 09 27 21:55 */ @Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder getPasswordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity.formLogin(). // 设置登陆行为方式为表单登陆 // 登陆请求参数设置 usernameParameter("username"). passwordParameter("password"). loginPage("/login.html"). // 设置登陆页面URL路径 loginProcessingUrl("/login.action"). // 设置表单提交URL路径 // successForwardUrl("/main.page"). // 设置认证成功跳转URL路径 POST请求 successHandler(new FarsAuthenticationSuccessHandler("https://www.acfun.cn/")). // 使用自定义的重定向登陆 // failureForwardUrl("/error.page"); // 设置认证失败跳转URL路径 POST请求 failureHandler(new FarsAuthenticationFailureHandler("/error.html")); // 跨域处理,不需要跳转了 httpSecurity.authorizeRequests(). regexMatchers(HttpMethod.POST, "正则表达式").permitAll(). // 还可以对符合正则表达式的请求方式进行要求,这个属性使用来制定请求的方式 antMatchers("/**/*.js", "/**/*.css", "/**/images/*.*").permitAll(). // 静态资源放行 antMatchers("/login.html").permitAll(). // 登陆页面允许任意访问 antMatchers("/error.html").permitAll(). // 失败跳转后重定向的页面也需要被允许访问 antMatchers("/admin.page").hasAnyAuthority("admin"). /*antMatchers("/vip-01.page").hasAnyAuthority("vip-01").*/ antMatchers("/vip-01.page").hasRole("vip-01"). // mvcMatchers("/main.page").servletPath("/xxx").permitAll(). // mvcMatchers资源放行匹配 // antMatchers("/xxx/main.page").permitAll(). // 或者多写MSP的前缀 anyRequest().authenticated(); // 其他请求均需要被授权访问 // CSRF攻击拦截关闭 httpSecurity.csrf().disable(); } }
重启项目运行:
现在VIP-01资源可以被正常的访问了:
案例演示只是使用了单一的角色和权限处理,但实际开发的环境下面更为复杂,Security对此也提供好了 多权限 + 多角色的API
四、IP地址判断:
在之前的JavaWeb的学习中,我们可以通过请求的HttpServletRequest对象中,获取发送的请求的客户端IP地址
注意在本机上的一些访问问题:
以不同的IP地址形式访问,服务端接受到的IP地址也是不一样的
1、第一种 http://localhost:8080/ 打印结果 0:0:0:0:0:0:0:1 2、第二种 http://127.0.0.1:8080/ 打印结果 127.0.0.1 3、第三种 http://192.168.43.180:8080/ 打印结果 192.168.43.180
这里新建一个资源,仅限于第三种IP地址符合的控制
<h3>Only 192.168.43.180 IP-Host Allowed</h3>
然后配置控制器和权限:
@RequestMapping("ip.page") public String toIpPage() { return "permit/ip"; }
看看非远程IP地址是否有效
antMatchers("/ip.page").hasIpAddress("192.168.43.180").
可以看到,使用Localhost是不被允许访问的
同样的127.0.0.1地址也不允许
只有最后的远程IP地址才能被允许访问:
五、自定义403处理方案
除了这样的403响应,前后端分离的处理是发送响应状态信息完成的,所以Security允许我们自己设置一个禁止访问的结果的处理
package cn.zeal4j.handler; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * @author Administrator * @file Spring-Security-Tutorial * @create 2020 09 28 14:37 */
@Component public class CustomAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException { httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN); httpServletResponse.setHeader("Content-Type", "application/json;charset=utf-8"); PrintWriter printWriter = httpServletResponse.getWriter(); // json数据 : { "status" : "error" , "message" : "权限不足,访问被禁止!!!" } printWriter.print("{ \"status\" : \"error\" , \"message\" : \"权限不足,访问被禁止!!!\" }"); printWriter.flush(); printWriter.close(); } }
然后在配置类中设置,403访问禁止的异常抛出:
package cn.zeal4j.configuration; import cn.zeal4j.handler.CustomAccessDeniedHandler; import cn.zeal4j.handler.FarsAuthenticationFailureHandler; import cn.zeal4j.handler.FarsAuthenticationSuccessHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.access.AccessDeniedHandler; /** * @author Administrator * @file IntelliJ IDEA Spring-Security-Tutorial * @create 2020 09 27 21:55 */ @Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired private AccessDeniedHandler accessDeniedHandler; @Bean public PasswordEncoder getPasswordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity.formLogin(). // 设置登陆行为方式为表单登陆 // 登陆请求参数设置 usernameParameter("username"). passwordParameter("password"). loginPage("/login.html"). // 设置登陆页面URL路径 loginProcessingUrl("/login.action"). // 设置表单提交URL路径 // successForwardUrl("/main.page"). // 设置认证成功跳转URL路径 POST请求 successHandler(new FarsAuthenticationSuccessHandler("https://www.acfun.cn/")). // 使用自定义的重定向登陆 // failureForwardUrl("/error.page"); // 设置认证失败跳转URL路径 POST请求 failureHandler(new FarsAuthenticationFailureHandler("/error.html")); // 跨域处理,不需要跳转了 httpSecurity.authorizeRequests(). regexMatchers(HttpMethod.POST, "正则表达式").permitAll(). // 还可以对符合正则表达式的请求方式进行要求,这个属性使用来制定请求的方式 antMatchers("/**/*.js", "/**/*.css", "/**/images/*.*").permitAll(). // 静态资源放行 antMatchers("/login.html").permitAll(). // 登陆页面允许任意访问 antMatchers("/error.html").permitAll(). // 失败跳转后重定向的页面也需要被允许访问 antMatchers("/admin.page").hasAnyAuthority("admin"). /*antMatchers("/vip-01.page").hasAnyAuthority("vip-01").*/ antMatchers("/vip-01.page").hasRole("vip-01"). antMatchers("/ip.page").hasIpAddress("192.168.43.180"). // mvcMatchers("/main.page").servletPath("/xxx").permitAll(). // mvcMatchers资源放行匹配 // antMatchers("/xxx/main.page").permitAll(). // 或者多写MSP的前缀 anyRequest().authenticated(); // 其他请求均需要被授权访问 // CSRF攻击拦截关闭 httpSecurity.csrf().disable(); httpSecurity.exceptionHandling().accessDeniedHandler(accessDeniedHandler); } }
403访问测试: