github
原文
背景
公司老舊項目(JSP)愈發臃腫,部分模塊已經分離(後臺改爲restful),通過掛載鏈接的方式集成
JSP項目集成shiro 導致restful項目也使用同類配置(未禁用session)
JSP與restful不在同一個域名下 每次使用分離的模塊需要重新登錄(傳個假token 然後根據這個token登錄返回可用token)
改造
前後端分離的項目禁用session 不使用sessionId (其實項目已經是傳了token到後臺,可用戶信息還是存儲到session中)
用戶權限信息
public class UserUtil {
public static ConcurrentHashMap<String, User> usermap = new ConcurrentHashMap<>(16 * 4);
/**
* 具體權限
*
* @author sunmj
* @date 2021/3/26
*/
public static ConcurrentHashMap<String, List<String>> permMap = new ConcurrentHashMap<String, List<String>>(16 * 4){
{
put("user", Arrays.asList("/user/queryUser", "/depart/queryDepart"));
}
};
/**
* 菜單權限
*
* @author sunmj
* @date 2021/3/26
*/
public static ConcurrentHashMap<String, List<String>> menupermMap = new ConcurrentHashMap<String, List<String>>(16 * 4){
{
put("user", Arrays.asList("/role/**"));
}
};
shiroConfig
@Slf4j
@Configuration
public class ShiroConfig {
/**
* 安全管理器
* 禁用session
*
* @author sunmj
* @date 2021/3/24
*/
@Bean
public DefaultWebSecurityManager securityManager(CustomAuthorizingRealm shiroRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 禁用sessionStorage
((DefaultSessionStorageEvaluator) ((DefaultSubjectDAO) securityManager.getSubjectDAO()).getSessionStorageEvaluator()).setSessionStorageEnabled(false);
// 禁用session會話調度器
DefaultSessionManager sessionManager = new DefaultSessionManager();
sessionManager.setSessionValidationSchedulerEnabled(false);
securityManager.setSessionManager(sessionManager);
// 禁止創建session
securityManager.setSubjectFactory(defaultWebSubjectFactory());
// 設置認證和授權服務
securityManager.setRealm(shiroRealm);
// 設置SecurityUtils的靜態方法 獲取用戶等使用 SecurityManager
SecurityUtils.setSecurityManager(securityManager);
return securityManager;
}
@Bean
public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 定義攔截器
Map<String, Filter> filterMap = new LinkedHashMap();
filterMap.put("token", new TokenFilter());
filterMap.put(DefaultFilter.perms.name(), new CustomPermissionsAuthorizationFilter());
shiroFilterFactoryBean.setFilters(filterMap);
// 定義url過濾
Map<String, String> filterChainDefinitionMap = new LinkedHashMap();
// 所有url都必須認證通過纔可以訪問
// authc 必須認證通過纔可以訪問
filterChainDefinitionMap.put("/login/**", DefaultFilter.anon.name());
// 其他所有請求全部走token驗證
filterChainDefinitionMap.put("/**", "token".concat(",").concat(DefaultFilter.perms.name()));
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 啓用shrio授權註解攔截方式,AOP式方法級權限檢查
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* 禁用seesion
*
* @author sunmj
* @date 2021/3/5
*/
public DefaultWebSubjectFactory defaultWebSubjectFactory(){
DefaultWebSubjectFactory defaultWebSubjectFactory = new DefaultWebSubjectFactory(){
@Override
public Subject createSubject(SubjectContext context) {
//不創建session
context.setSessionCreationEnabled(false);
return super.createSubject(context);
}
};
return defaultWebSubjectFactory;
}
自定義
AuthenticationToken
public class CustomAuthenticationToken implements AuthenticationToken {
private String token;
public CustomAuthenticationToken(String token) {
this.token = token;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
@Override
public Object getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token;
}
}
AuthorizingRealm
@Slf4j
@Component
public class CustomAuthorizingRealm extends AuthorizingRealm {
public CustomAuthorizingRealm() {
super(new AllowAllCredentialsMatcher());
}
@Override
public boolean supports(AuthenticationToken authenticationToken) {
return authenticationToken instanceof CustomAuthenticationToken;
}
/**
* 認證
*
* @author sunmj
* @date 2021/3/24
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
CustomAuthenticationToken authenticationToken = (CustomAuthenticationToken) token;
User user = UserUtil.usermap.get(authenticationToken.getToken());
// log.info("doGetAuthenticationInfo user {}", JSON.toJSONString(user));
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPasswd(), this.getName());
return info;
}
/**
* 授權
*
* @author sunmj
* @date 2021/3/24
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
User user = (User)principals.getPrimaryPrincipal();
// log.info("doGetAuthorizationInfo user {}", JSON.toJSONString(user));
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
String account = user.getAccount();
//授權
info.addStringPermissions(UserUtil.permMap.get(account));
info.addStringPermissions(UserUtil.menupermMap.get(account));
return info;
}
/**
* 獲取用戶授權信息
*
* @author sunmj
* @date 2021/3/25
*/
public AuthorizationInfo queryAuthorizationInfo(PrincipalCollection principals){
return this.getAuthorizationInfo(principals);
}
}
BasicHttpAuthenticationFilter
認證授權
public class TokenFilter extends BasicHttpAuthenticationFilter {
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
String token = request.getParameter("token");
if(StringUtils.isEmpty(token)){
this.response401(response, "token不存在");
return false;
}
User user = UserUtil.usermap.get(token);
if(user == null){
this.response401(response, "登錄超時");
return false;
}
Subject subject = SecurityUtils.getSubject();
CustomAuthenticationToken authenticationToken = new CustomAuthenticationToken(token);
subject.login(authenticationToken);
return true;
}
/**
* 無需轉發,直接返回Response信息
*/
private void response401(ServletResponse response, String msg) {
HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json; charset=utf-8");
try (PrintWriter out = httpServletResponse.getWriter()) {
JSONObject jo = new JSONObject();
jo.put("msg", msg);
out.append(jo.toJSONString());
} catch (IOException e) {
log.error("直接返回Response信息出現IOException異常:{}", e.getMessage());
throw new RuntimeException("直接返回Response信息出現IOException異常:" + e.getMessage());
}
}
}
AuthorizationFilter
鑑權
@Slf4j
public class CustomPermissionsAuthorizationFilter extends AuthorizationFilter {
/**
* 開始鑑權
*
* @author sunmj
* @date 2021/3/25
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
Subject subject = SecurityUtils.getSubject();
User user = (User)subject.getPrincipals().getPrimaryPrincipal();
List<String> menulist = UserUtil.menupermMap.get(user.getAccount());
HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
String requestURI = httpServletRequest.getRequestURI();
//目錄權限
if(!CollectionUtils.isEmpty(menulist)){
boolean permitted = false;
for (String m : menulist) {
permitted = this.pathsMatch(m, requestURI);
}
if(permitted){
return permitted;
}
}
//接口權限
return subject.isPermitted(requestURI);
}
/**
* 鑑權失敗
*
* @author sunmj
* @date 2021/3/25
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
this.response403(response, "權限不足");
return false;
}
/**
* 無需轉發,直接返回Response信息
*/
private void response403(ServletResponse response, String msg) {
HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
httpServletResponse.setStatus(HttpStatus.FORBIDDEN.value());
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json; charset=utf-8");
try (PrintWriter out = httpServletResponse.getWriter()) {
JSONObject jo = new JSONObject();
jo.put("msg", msg);
out.append(jo.toJSONString());
} catch (IOException e) {
log.error("直接返回Response信息出現IOException異常:{}", e.getMessage());
throw new RuntimeException("直接返回Response信息出現IOException異常:" + e.getMessage());
}
}
}
接口測試
登錄 http://127.0.0.1:13010/login/loginByAccount?account=user
調用接口 http://127.0.0.1:13010/user/queryUser (沒有token)
調用接口 http://127.0.0.1:13010/user/queryUser?token=cadcf757f5fe4ee9b66d95562cd9e7c2
權限不足 http://127.0.0.1:13010/user/queryAuthorizationInfo?token=cadcf757f5fe4ee9b66d95562cd9e7c2