spring shiro禁用session

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章