权限表达式
通过源码分析可知,最终的配置都会转成ExpressionUrlAuthorizationConfigurer.AuthorizedUrl并进行投票决定。
常见表达式如下:
如何写联合表达式呢?就是既满足A条件,也满足B条件
.antMatchers("xx").access("hasRole('ROLE_USER') and hasRole('ROLE_SUPER')")
这个都是spring自己的权限表达式,那么我们是否可以自定义实现自己的权限规则呢?可以的!这个我们下一章再看怎么实现。
权限业务配置分离
如何将权限模块配置和业务模块配置想分离呢?因为权限模块并不清楚业务模块的权限拦截规则。
实现思路如下:
- 提供AuthorizeConfigProvider接口,提供权限设置
- 权限模块的通用配置实现该接口,然后进行配置,业务模块按需也实现该接口进行配置
- 最后使用 AuthorizeConfigManager类来管理所有的AuthorizeConfigProvider实现
- 拿到所有的配置后,进行统一设置
代码实现如下:
权限控制接口及实现
package com.rui.tiger.auth.core.authorize;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
/**
* 自定义权限控制接口
* @author CaiRui
* @date 2019-05-06 12:02
*/
public interface AuthorizeConfigProvider {
/**
* @param config
* @return 返回的boolean表示配置中是否有针对anyRequest的配置。在整个授权配置中,
* 应该有且仅有一个针对anyRequest的配置,如果所有的实现都没有针对anyRequest的配置,
* 系统会自动增加一个anyRequest().authenticated()的配置。如果有多个针对anyRequest
* 的配置,则会抛出异常。
*/
boolean config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config);
}
通用权限设置
package com.rui.tiger.auth.core.authorize;
import com.rui.tiger.auth.core.properties.SecurityConstants;
import com.rui.tiger.auth.core.properties.SecurityProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.stereotype.Component;
/**
* 通用权限接口配置管理
* @author CaiRui
* @date 2019-05-06 12:08
*/
@Component
@Slf4j
@Order(Integer.MIN_VALUE)
public class CommonAuthorizeConfigProvider implements AuthorizeConfigProvider {
@Autowired
private SecurityProperties securityProperties;
@Override
public boolean config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config) {
config.antMatchers(
SecurityConstants.DEFAULT_UNAUTHENTICATION_URL,//权限认证
SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE,//手机
SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_OPENID,//openId
securityProperties.getBrowser().getLoginPage(),//登录页面
SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX+"/*",// /captcha/* 验证码放行
securityProperties.getBrowser().getSignupUrl(),
securityProperties.getBrowser().getLoginOut(),
securityProperties.getBrowser().getSession().getInvalidSessionUrl()
).permitAll();//放行
return false;
}
}
业务模块权限设置
package com.rui.tiger.auth.demo.security;
import com.rui.tiger.auth.core.authorize.AuthorizeConfigProvider;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.stereotype.Component;
/**
* 业务模块权限配置实现
* @author CaiRui
* @date 2019-05-06 12:16
*/
@Component
@Order(Integer.MAX_VALUE)
public class DemoAuthorizeConfigProvider implements AuthorizeConfigProvider {
@Override
public boolean config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config) {
config.antMatchers(
"/user/regist", // 注册请求
"/error",
"/connect/*",
"/auth/*",
"/signin",
"/social/signUp", // app注册跳转服务
"/swagger-ui.html",
"/swagger-ui.html/**",
"/webjars/**",
"/swagger-resources/**",
"/v2/**"
).permitAll();
/*
利用@Order注解实现CommonAuthorizeConfigProvider中的放行路径不需要自定义权限验证
*/
config.anyRequest()
.access("@rbacService.hasPermission(request,authentication)");
return true;
}
}
权限接口管理器及实现
package com.rui.tiger.auth.core.authorize;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
/**
* 权限控制接口持有管理者
* @author CaiRui
* @date 2019-05-06 12:05
*/
public interface AuthorizeConfigManager {
void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config);
}
package com.rui.tiger.auth.core.authorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Set;
/**
* @author CaiRui
* @date 2019-05-06 12:12
*/
@Component
public class DefaultAuthorizeConfigManager implements AuthorizeConfigManager {
//有依赖顺序 通过@Order控制 不用set
@Autowired
private List<AuthorizeConfigProvider> providers;
@Override
public void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config) {
boolean existAnyRequestConfig = false;
String existAnyRequestConfigName = null;
for (AuthorizeConfigProvider authorizeConfigProvider : providers) {
boolean currentIsAnyRequestConfig = authorizeConfigProvider.config(config);
if (existAnyRequestConfig && currentIsAnyRequestConfig) {
throw new RuntimeException("重复的anyRequest配置:" + existAnyRequestConfigName + ","
+ authorizeConfigProvider.getClass().getSimpleName());
} else if (currentIsAnyRequestConfig) {
existAnyRequestConfig = true;
existAnyRequestConfigName = authorizeConfigProvider.getClass().getSimpleName();
}
}
if(!existAnyRequestConfig){
config.anyRequest().authenticated();
}
}
}
浏览器模块配置进行调整(app模块同理),就是将原来的配置抽离出去。
@Override
protected void configure(HttpSecurity http) throws Exception {
/**
* 表单密码配置
*/
applyPasswordAuthenticationConfig(http);
http
.apply(captchaSecurityConfig)
.and()
.apply(smsAuthenticationSecurityConfig)
.and()
.apply(tigerSpringSocialConfigurer)
.and()
.rememberMe()
.tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(securityProperties.getBrowser().getRemberMeSeconds())
.userDetailsService(userDetailsService)
.and()
.sessionManagement()
.invalidSessionStrategy(invalidSessionStrategy)//session失效策略
.maximumSessions(securityProperties.getBrowser().getSession().getMaximumSessions())//最大session并发数
.maxSessionsPreventsLogin(securityProperties.getBrowser().getSession().isMaxSessionsPreventsLogin())//true达到并发数后阻止登录,false 踢掉之前的登录
.expiredSessionStrategy(sessionInformationExpiredStrategy)//并发策略
.and()
.and()
.logout()
.logoutUrl("/loginOut") //默认logout
//.logoutSuccessUrl("") url和Handler只能配置一个
.logoutSuccessHandler(tigerLogoutSuccessHandler)
.deleteCookies("JSESSIONID")//清楚cook键值
.and()
.csrf().disable();
//配置管理
authorizeConfigManager.config(http.authorizeRequests());
}