SpringSecurity角色和权限授权
在登录成功之后,会有少部分资源不会给没有权限的人进行开放,比如说爱奇艺会员,只有会员才能看会员专享的电影,SpringBoot+SpringSecurity整合(二)中将当前用户的角色和权限加入到了UserUserDetails中,这样就可以进行角色和权限的判断了
@Override
protected void configure(HttpSecurity http) throws Exception {
// 表单登录
http.formLogin()
// 自定义表单接收参数
.usernameParameter("username")
.passwordParameter("password")
// 设置登录页面
.loginPage("/login.html")
// 登录的请求必须和表单登录的url一致
.loginProcessingUrl("/login")
// 登录成功的页面(这里不能直接写页面要通过POST请求跳到页面)
.defaultSuccessUrl("/toMain")
// 登录成功的控制器
//.successHandler(new MyAuthenticationSuccessHandler("http://www.baidu.com"))
// 登录失败的页面(这里不能直接写页面要通过POST请求跳到页面)
.failureForwardUrl("/toError");
// 登录失败的控制器
//.failureHandler(new MyAuthenticationFailureHandler("http://www.alibaba.com"));
// 授权
http.authorizeRequests()
// 对登录页面、登录成功的页面,登录失败的请求不做授权访问
.antMatchers("/login.html","/error.html").permitAll()
// 通过正则表达式进行放行放行后缀为png的文件
.regexMatchers(".+[.]png").permitAll()
// 登录成功之后具有某一个角色才能访问该页面(字母严格区分大小写),此处不用添加ROLE_前缀,这里会自动添加上ROLE_前缀,
// 在mySecurityUserDetails中添加角色时需要添加上ROLE_前缀
.regexMatchers("/main1.html").hasRole("ADMIN")
// 登录成功之后具有某一个权限才能访问该页面(字母严格区分大小写)
.regexMatchers("/main1.html").hasAuthority("admin")
// 其他所有的请求必须得经过授权,必须登录
.anyRequest().authenticated();
// 使用自定义的access进行权限控制
// 关闭CSRF跨域
http.csrf().disable();
}
还可以使用
hasAnyRole()
和hasAnyAuthority()
进行角色和权限的判断,hasAnyRole()
方法要放入多个角色只要具有其中一种就可以进行相关资源的访问,hasAnyAuthority()
方法也可以放多个权限用逗号隔开,只要有其中一种权限就可以访问
在SpringSecurity没有权限的页面可以自己定制
通过重写
AccessDeniedHandler
这个接口的handle方法,然后再SpringSecurity配置类中注入此类在configure(HttpSecurity http)
方法中添加代码
// 统一的403页面
http.exceptionHandling()
.accessDeniedHandler(myAccessDeniedHandler);
@Component
public class MyAccessDeniedHandler 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");
httpServletResponse.setCharacterEncoding("utf-8");
PrintWriter writer = httpServletResponse.getWriter();
writer.write("{\"status\":\"403\",\"msg\":\"没有权限访问,请联系管理员\"}");
writer.flush();
writer.close();
}
}
没有权限访问时会出现当前页面
自定义Access
通过观察源码可以发现
hasRole()
、hasAuthority()
、hasAnyRole()
和hasAnyAuthority()
,这些方法在低层都是通过access
方法去实现的,如果SpringSecurity内置的不满足需求可以通过以下方法去自定义Access
编写一个MyAccessService
接口
public interface MyAccessService {
public boolean myUri(HttpServletRequest request, Authentication authentication);
}
进行实现并加上
@Service
注解
@Service
public class MyAccessServiceImpl implements MyAccessService {
@Override
public boolean myUri(HttpServletRequest request, Authentication authentication) {
Object principal = authentication.getPrincipal();
if(principal instanceof UserDetails){
UserDetails userDetails = (UserDetails) principal;
Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
boolean contains = authorities.contains(new SimpleGrantedAuthority(request.getRequestURI()));
return contains;
}
return false;
}
}
将以下代码加入到末尾将
.anyRequest().authenticated()
进行替换,这样其他的资源访问权限就不是登录之后就可以访问,变成了这个人要有request.getRequestURI()
这个uri的权限就是要在当前用户的权限中加入request.getRequestURI()
的值才能访问
// 自定义access
.anyRequest().access("@myAccessServiceImpl.myUri(request,authentication)");
跨栈请求伪造
介绍
CSRF(Cross-Site Request Forgery,跨站点伪造请求)是一种网络攻击方式,该攻击可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击站点,从而在未授权的情况下执行在权限保护之下的操作,具有很大的危害性。具体来讲,可以这样理解CSRF攻击:攻击者盗用了你的身份,以你的名义发送恶意请求,对服务器来说这个请求是完全合法的,但是却完成了攻击者所期望的一个操作,比如以你的名义发送邮件、发消息,盗取你的账号,添加系统管理员,甚至于购买商品、虚拟货币转账等。
实现
在SpringSecurity中默认打开了_csrf的功能当前项目为了方便关闭了此功能,如果使用此功能就必须在访问时加入参数,默认的_csrf参数是一个token是有SpringSecurity自动生成的在页面访问时必须加入以下的代码才能被SpringSecurity识别
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
注释掉这一行代码就打开csrf的功能
// 关闭CSRF跨域
// http.csrf().disable();