前言
上一章讲解了Spring Boot集成Spring Security实现了登录认证,本文将讲解Spring Security实现权限控制
实现权限控制有如下几种方式:
- 表达式控制URL权限
- 注解方式控制权限
- 动态控制权限
表达式控制URL权限
Spring Security支持在URL和方法权限控制时使用SpEL表达式,如果表达式返回值为true则表示需要对应的权限,否则表示不需要对应的权限。Spring Secuity提供了如下的SPEL表达式
权限配置
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/app/sys/user/roleTest").hasRole("user")
//未授权
.exceptionHandling().accessDeniedPage( "/403" );
}
说明:访问/app/sys/user/roleTest请求需要user角色,否则会跳转到403
测试结果
用户登录成功后,访问/app/sys/usr/roleTest,没有权限则跳转到403页面
采用注解表达式权限控制
Spring Security也提供了如下注解,可以采用注解表达式的方式进行权限控制
- @PreAuthorize:方法执行前进行权限检查
- @PostAuthorize:方法执行后进行权限检查
- @Secured:类似于 @PreAuthorize
权限配置
通过注解的方式,首先需要开启注解,即设置securedEnabled和prePostEnabled指为true
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
}
示例代码
//用户id为admin
@PreAuthorize("principal.username.equals('admin')")
public String hello() {
return "hello";
}
//用户角色为admin的才能访问
@PreAuthorize("hasRole('ROLE_admin')")
@RequestMapping("/roleTest")
public String roleTest()
{
return "成功";
}
注意:Spring Security中的角色默认是以Role_角色id
测试结果
用户登录成功后,访问/app/sys/usr/roleTest,没有权限则跳转到403页面
动态控制权限
动态权限主要通过重写拦截器和决策器来实现。
权限验证流程
实现步骤
自定义元数据加载器
@Component
public class CustomInvocationSecurityMetadataSourceService implements
FilterInvocationSecurityMetadataSource
{
private Logger logger =LoggerFactory.getLogger(getClass());
@Autowired
private UserMapper userMapper;
public Collection<ConfigAttribute> getAllConfigAttributes()
{
return null;
}
private List<String> allowedRequest(){
return Arrays.asList("/login","/css/**","/fonts/**","/js/**","/scss/**","/img/**");
}
/**
* 判断当前请求是否在允许请求的范围内
* @param fi 当前请求
* @return 是否在范围中
*/
private boolean isMatcherAllowedRequest(FilterInvocation fi){
return allowedRequest().stream().map(AntPathRequestMatcher::new)
.filter(requestMatcher -> requestMatcher.matches(fi.getHttpRequest()))
.toArray().length > 0;
}
/**
* 此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中则返回给 decide方法。
*/
public Collection<ConfigAttribute> getAttributes(Object o)
throws IllegalArgumentException
{
// 请求url
FilterInvocation filterInvocation = (FilterInvocation) o;
String requestUrl = filterInvocation.getRequestUrl();
logger.info("request Url:{}",requestUrl);
//过滤特殊的url
if (isMatcherAllowedRequest(filterInvocation))
{
return null;
}
List<String> roles = getRolesByRes(requestUrl);
logger.info("{} 对应的角色列表{}",requestUrl,roles);
if (CollectionUtils.isEmpty(roles))
{
return null;
}
String[] attributes = roles.toArray(new String[0]);
return SecurityConfig.createList(attributes);
}
public boolean supports(Class<?> arg0)
{
return true;
}
public List<String> getRolesByRes(String url)
{
//加载所有权限数据
List<Func> funcs = userMapper.getRolesByRes(url);
if(funcs==null || funcs.isEmpty())
{
return null;
}
List<String> roles =new ArrayList<>();
for (Func func : funcs)
{
String roleId ="ROLE_"+func.getRoleId();
roles.add(roleId);
}
return roles;
}
}
说明:查询当前访问的URL所对应的角色信息,如果存在则返回给AccessDecisionManager处理,如果为空则不处理。
自定义决策管理器
/**
* @Description 自定义权限决策管理器
*/
@Component
public class CustomAccessDecisionManager implements AccessDecisionManager
{
private final static Logger logger = LoggerFactory.getLogger(CustomAccessDecisionManager.class);
@Override
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) throws AccessDeniedException,
InsufficientAuthenticationException
{
if (null == configAttributes || configAttributes.isEmpty())
{
logger.error("configAttributes is emtpy");
return;
}
else
{
//判断是否有权限
for(Iterator<ConfigAttribute> iter = configAttributes.iterator(); iter.hasNext(); )
{
String needRole = iter.next().getAttribute();
for(GrantedAuthority ga : authentication.getAuthorities()) {
if(needRole.trim().equals(ga.getAuthority().trim()))
{
return;
}
}
}
//没有权限报错
throw new AccessDeniedException("当前访问没有权限");
}
}
@Override
public boolean supports(ConfigAttribute configAttribute)
{
return true;
}
@Override
public boolean supports(Class<?> clz)
{
return true;
}
}
说明:AccessDecisionManager 有三个默认实现:
AffirmativeBased 基于肯定的决策器。 用户持有一个同意访问的角色就能通过。 ConsensusBased 基于共识的决策器。 用户持有同意的角色数量多于禁止的角色数。 UnanimousBased 基于一致的决策器。 用户持有的所有角色都同意访问才能放行。
默认采用的是第一种投票决策器
未登录权限处理
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint
{
private Logger logger =LoggerFactory.getLogger(CustomAuthenticationEntryPoint.class);
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException
{
logger.info("user not login AuthenticationEntryPoint");
response.setCharacterEncoding("utf-8");
response.setContentType("text/javascript;charset=utf-8");
Map<String,String> map = new HashMap<>();
map.put("code", "403");
map.put("msg","用户未登录,无访问权限");
response.getWriter().print(map);
}
}
无权限访问处理
public class CustomAccessDeineHandler implements AccessDeniedHandler
{
private Logger logger = LoggerFactory.getLogger(CustomAccessDeineHandler.class);
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException ex) throws IOException, ServletException
{
logger.info("user have not permisson CustomAccessDeineHandler");
response.setCharacterEncoding("utf-8");
response.setContentType("text/javascript;charset=utf-8");
Map<String,String> map = new HashMap<>();
map.put("code", "403");
map.put("msg","用户无访问权限");
response.getWriter().print(JSONObject.toJSONString(map));
}
}
说明:用户未登录,且请求的URL不是的通用的,则会进行拦截。
修改Spring Security配置
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login","/register","/login-error","/register-save","/error","/css/**","/js/**").permitAll()
//任何请求需要认证
.anyRequest().authenticated()
// 登录页面
.and().formLogin().loginPage("/login")
.failureUrl("/login-error").defaultSuccessUrl("/index")
//登出
.and().logout().logoutUrl("/logout")
//关闭跨域访问
.and().csrf().disable()
//未授权页面访问
.exceptionHandling().accessDeniedPage( "/403" )
//设置验证
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
o.setSecurityMetadataSource(customInvocationSecurityMetadataSourceService);
o.setAccessDecisionManager(customAccessDecisionManager);
return o;
}
});
//未登录验证、鉴权异常
http.exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint())
.accessDeniedHandler(new CustomAccessDeineHandler());
}
说明:用户登录,无权限访问的请求,则会进行拦截。
测试
系统配置了User和admin角色以及user和admin用户
用户未登录
用户未登录,访问/app/sys/user/list,显示如下信息
用户已登录,user用户访问
用户已登录,无权限访问/app/sys/user/list,
用户已登录,admin用户访问权限
用户已登录,有权限访问/app/sys/user/list
总结
本文介绍了Spring Security实现权限控制的几种方法,如有疑问,可以随时反馈交流。