权限控制
SpringSecurity中当无权访问某个资源时,会返回403错误。
- 第一种:
在SpringSecurity的配置类中指定。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
1. SpringSecurity配置类
**/
@Configuration
@EnableWebSecurity
public class WebAppSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 配置资源放行和拦截
*
* @param security
* @throws Exception
*/
@Override
protected void configure(HttpSecurity security) throws Exception {
security.authorizeRequests()
.antMatchers("地址") // 配置需要角色访问的请求地址
.hasRole("角色编码") // 设置访问该地址需要的角色
.antMatchers("地址") // 配置需要权限访问的地址
.hasAuthority("权限名称") // 设置访问该地址需要的权限
.anyRequest() // 其他请求
.authenticated() // 认证后访问
;
}
}
注意,所有权限设置需要在anyRequest的设置之前,否则不生效。
- 第二种:
在对应controller的方法上使用注解。这种方式需要先在SpringSecurity配置类中使用@EnableGlobalMethodSecurity(prePostEnabled = true)开启。
- 在SpringSecurity配置类中开启
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
* SpringSecurity配置类
*/
@Configuration
@EnableWebSecurity
// 启用全局[方法权限控制]功能,并设置prePostEnabled为true。保证@PreAuthority/@PostAuthority/@PreFilter/@PostFilter生效
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebAppSecurityConfig extends WebSecurityConfigurerAdapter {
}
- controller方法中使用:
/* 指定角色 */
@PreAuthorize("hasRole('角色编码')")
@RequestMapping("请求地址")
public String saveAdmin(Admin admin) {
return "";
}
/* 指定权限 */
@PreAuthorize("hasAuthority('权限名称')")
@RequestMapping("请求地址")
public String saveAdmin(Admin admin) {
return "";
}
注意:这里指定角色时,是不用加“ROLE_”前缀的。
- 第三种:
动态指定需要设置的请求地址,并设置对应的角色和权限。 - 实现WebSecurityConfigurerAdapter 接口的配置类:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MySecurityMetadataSource metadataSource;
@Autowired
private MyAccessDecisionManager decisionManager;
// 其他需要的配置信息可以在后面加上
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
// 配置资源(路径)和权限的对应关系
object.setSecurityMetadataSource(metadataSource);
// 设置资源(路径)拦截处理,即判断用户是否有访问资源的权限
object.setAccessDecisionManager(decisionManager);
return object;
}
})
.and()
.csrf()
.disable()
.formLogin()
.permitAll()
;
}
}
- 实现FilterInvocationSecurityMetadataSource接口,自定义匹配url权限的类:
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.List;
@Slf4j
@Component
public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
// 该方法的作用是获取请求的url在数据库中配置的权限的集合,方法的具体事实需要根据设计修改
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
// 从数据库加载url对应的角色和权限信息
List<ConfigAttribute> list = loadResourceDefine(object);
// 获取当前请求资源
FilterInvocation fi = (FilterInvocation) object;
// 获取所有的url
String url = fi.getRequestUrl();
// url匹配器,这里需要根据具体设计情况进行实现,matches方法是用来匹配请求的url是否是数据库配置的url的子域。如果设计是全匹配,则这步可以省略,直接做相等判断。
RequestMatcher requestMatcher = new AntPathRequestMatcher(url + "**");
if(requestMatcher.matches(fi.getHttpRequest())){
return list;
}
return null;
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> aClass) {
// 设置为true
return true;
}
}
- 实现AccessDecisionManager接口,自定义权限校验的类。
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.Iterator;
/**
* 决策管理器
* decide 方法决定用户是否有权访问url
*/
@Slf4j
@Component
public class MyAccessDecisionManager implements AccessDecisionManager {
/**
* 判断用户是否有访问指定资源的权限【直接返回表示能够访问,不能访问则抛异常】
*
* @param authentication 当前登录用户拥有的权限,如果没有登录则为游客,登录了则为权限
* @param object 指定的资源,即url
* @param configAttributes 该资源设置的权限集合
* @throws AccessDeniedException
* @throws InsufficientAuthenticationException
*/
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
// 如果该资源没有设置任何权限,则可以访问
if (configAttributes == null) {
return;
}
log.info("访问地址:" + object.toString());
Iterator<ConfigAttribute> iterator = configAttributes.iterator();
while (iterator.hasNext()) {
ConfigAttribute configAttribute = iterator.next();
// 获取单个权限信息
String attribute = configAttribute.getAttribute();
// 和用户拥有的权限进行匹配
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority authority : authorities) {
// 如果能匹配上,说明该用户拥有访问权限
String authority1 = authority.getAuthority();
if (attribute.equals(authority1)) {
return;
}
}
}
// 如果都没有匹配上,则说明用户没有访问此资源的权限
throw new AccessDeniedException("抱歉!您没有访问该资源的权限!");
}
@Override
public boolean supports(ConfigAttribute configAttribute) {
// 返回true
return false;
}
@Override
public boolean supports(Class<?> aClass) {
// 返回true
return false;
}
}
- 设置一个无权访问的跳转页面(在SpringSecurity配置类中设置):
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
* SpringSecurity配置类
**/
@Configuration
@EnableWebSecurity
public class WebAppSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 配置资源放行和拦截
*
* @param security
* @throws Exception
*/
@Override
protected void configure(HttpSecurity security) throws Exception {
security.authorizeRequests()
.antMatchers("地址") // 配置需要角色访问的请求地址
.hasRole("角色编码") // 设置访问该地址需要的角色
.antMatchers("地址") // 配置需要权限访问的地址
.hasAuthority("权限名称") // 设置访问该地址需要的权限
.anyRequest() // 其他请求
.authenticated() // 认证后访问
.and()
.exceptionHandling() // 设置无权访问时异常处理
// .accessDeniedPage("错误页面地址") // 直接跳转到页面,如果需要做处理则使用下面这个
.accessDeniedHandler(new AccessDeniedHandler() {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
// 设置页面提示信息
request.setAttribute("errorMsg", "错误提示信息");
// 请求转发
request.getRequestDispatcher("/WEB-INF/page/common/system-error.jsp").forward(request, response);
}
}) // 处理方法[这里是返回一个错误提示页面]
;
}
}
注意:需要将springMVC的异常拦截器取消,否则使用注解时,异常会被springMVC拦截,导致无法到达SpringSecurity中定义的处理方法中。因为在方法中配置时,请求到达SpringSecurity中没有经过springMVC,但是基于注解的则是已经经过了springMVC。
- 页面上获取错误提示信息
<h3>
<!-- SpringSecurity无权访问提示 -->
${requestScope.errorMsg}
</h3>
一般情况,第一种 和 第二种虽然可以解决问题,但是是硬编码写死的,框架中一般不采取。