Spring Boot+Shiro+JWT实现多Realm时Filter异常捕捉

近日,大叔接公司需求,搭建前后端分离的后台管理模块接口。
简述登陆模块相关需求:

  • 用户输入用户名和密码获取JWTToken令牌
  • 用户使用JWTToken令牌访问后端业务接口

大叔简单说下Springboot集成Shiro的过程:
1.在pom.xml引入Shiro

<!-- Shiro使用Srping框架 -->
<dependency>
	<groupId>org.apache.shiro</groupId>
	<artifactId>shiro-spring</artifactId>
</dependency>

2.写自己的Realm:UserRealm.java,JWTRealm.java,

/**
*	因为UserRealm只用于登陆验证故继承AuthenticatingRealm就好了
**/
public class UserRealm extends AuthenticatingRealm {
	 /**
     *  该方法用于多Realm认证时识别需要使用哪一个Realm
     */ 
	@Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof UsernamePasswordToken;
    } 
	/**
	*	该方法用于登陆身份验证
	**/
    @Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		//TODO	根据自己的验证需要写登陆验证
     }
}
/**
*	JWTRealm既要验证身份,又要做权限认证,所以继承AuthorizingRealm 
**/
public class JWTRealm extends AuthorizingRealm {
	/**
	*	该方法用于多Realm认证时识别需要使用哪一个Realm
	**/
	@Override
	public boolean supports(AuthenticationToken token) {
		return token instanceof JwtToken;
	}
	
	/**
	*	权限 权限验证时会执行到这里
	**/	
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		//TODO 根据自己的设计写权限
	}
	/**
	*	该方法用于JWTToken验证
	**/
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
		//TODO	根据自己的验证需要写验证
	}
}

3.写JWTFilter.java

public class JwtFilter extends BasicHttpAuthenticationFilter {
	@Override
	protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
		//TODO
		return true;
	}
	
	@Override
	protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
		//TODO 
		return false;
	}
	
}

4.写ShiroConfig.java

/**
*	加载权限配置
**/
@Configuration
public class ShiroConfig {
	/**
	 * 注册shiro的Filter,拦截请求
	 */
	@Bean
	public FilterRegistrationBean<Filter> filterRegistrationBean(DefaultWebSecurityManager securityManager)
			throws Exception {
		FilterRegistrationBean<Filter> filterRegistration = new FilterRegistrationBean<Filter>();
		filterRegistration.setFilter((Filter) shiroFilter(securityManager).getObject());
		filterRegistration.addInitParameter("targetFilterLifecycle", "true");
		filterRegistration.setAsyncSupported(true);
		filterRegistration.setEnabled(true);
		filterRegistration.setDispatcherTypes(DispatcherType.REQUEST);
		return filterRegistration;
	}

	@Bean
	public DefaultWebSecurityManager securityManager() {
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		// 设置realm,这里不设置的话会报错
		// One or more realms must be present to execute an authentication attempt. One
		// or more realms must be present to execute an authentication attempt.
		securityManager.setAuthenticator(authenticator());
		securityManager.setAuthorizer(authorizer());
		return securityManager;
	}
	
	/**
	* 用于用户名密码登录时认证的realm
	*/
	@Bean("userRealm")
	public Realm userRealm() {
		UserRealm userRealm = new UserRealm();
		return userRealm;
	}

	/**
	* 用于JWT token认证的realm
	*/
	@Bean("jwtRealm")
	public Realm jwtRealm() {
		JWTRealm jwtRealm= new JWTRealm();
		return jwtRealm;
	}
	 /**
	 * 初始化Authenticator 认证器 身份认证
	 */
	@Bean
	public Authenticator authenticator() {
		ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();//留意这一行哟
		// 设置两个Realm,一个用于用户登录验证;一个用于jwt token的认证和访问权限获取
		authenticator.setRealms(Arrays.asList(jwtRealm(), userRealm()));
		// 设置多个realm认证策略,一个成功即跳过其它的
		authenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy());
		return authenticator;
	}
	
	/**
	 * 初始化authorizer 认证器 权限认证
	 * @return
	 */
	@Bean
	public Authorizer authorizer() {
		ModularRealmAuthorizer authorizer = new ModularRealmAuthorizer();//这里的
		authorizer.setRealms(Arrays.asList(jwtShiroRealm()));
		return authorizer;
	}
	
	/**
	* 禁用session, 不保存用户登录状态。保证每次请求都重新认证。
	* 需要注意的是,如果用户代码里调用Subject.getSession()还是可以用session,如果要完全禁用,要配合下面的noSessionCreation的Filter来实现
	*/
	@Bean
	protected SessionStorageEvaluator sessionStorageEvaluator() {
		DefaultWebSessionStorageEvaluator sessionStorageEvaluator = new DefaultWebSessionStorageEvaluator();
		sessionStorageEvaluator.setSessionStorageEnabled(false);
		return sessionStorageEvaluator;
	}
	
	/**
	 * 设置过滤器,将自定义的Filter加入
	 */
	@Bean("shiroFilter")
	public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
		ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
		factoryBean.setSecurityManager(securityManager);
		// 添加过滤器
		Map<String, Filter> filterMap = new HashMap<String, Filter>();
		// JWT过滤器
		filterMap.put("jwtFilter", jwtFilter());// JwTfilter
		factoryBean.setFilters(filterMap);
		// 拦截器
		factoryBean.setFilterChainDefinitionMap(shiroFilterChainDefinition().getFilterChainMap()); 
		return factoryBean;
	}

	@Bean
	protected ShiroFilterChainDefinition shiroFilterChainDefinition() {
		DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition(); 
		chainDefinition.addPathDefinition("/login", "noSessionCreation,anon"); // login不做认证,noSessionCreation的作用是用户在操作session时会抛异常
		chainDefinition.addPathDefinition("/**", "noSessionCreation,jwtFilter"); // 默认进行用户鉴权
		return chainDefinition;
	}

	// 不要加@Bean注解,不然spring会自动注册成filter,我们这里是手动注入
	protected JwtFilter jwtFilter() {
		return new JwtFilter();
	}
	/**
	* 开启Shiro注解通知器
	*/
	@Bean
	public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
			@Qualifier("securityManager") SecurityManager securityManager) {
		AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
		authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
		return authorizationAttributeSourceAdvisor;
	}

	@Bean
	public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
		DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
		creator.setProxyTargetClass(true);
		return creator;
	}
}

好了,到这里我们可以算作是完成了Shiro的集成工作了


随便写一段测试代码

@RestController
public class TestController {
	@GetMapping("/helloWorld")
	@RequiresPermissions("system:test:hello")
	public String helloWorld() {
		return “hello world";
	}
}

启动项目,请求这个接口试试看吧。


在实现了多Realm的登陆之后,发现当JWTRealm身份验证报错时,在JWTFilter获取到的异常类型都是AuthenticationException,而导致不能再JWTRealm中根据不同的异常做不同的处理。
怎么办呢?跟踪异常抛出流程发现多Realm时,异常在ModularRealmAuthenticator中会被处理掉,统一抛出AuthenticationException。所以大叔发现重写ModularRealmAuthenticator中的doMultiRealmAuthentication方法就好了

public class MultiRealmAuthenticator extends ModularRealmAuthenticator {
	private static final Logger log = LoggerFactory.getLogger(MultiRealmAuthenticator.class);
	@Override
	protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token)
			throws AuthenticationException {
		AuthenticationStrategy strategy = getAuthenticationStrategy();
		AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);
		if (log.isTraceEnabled()) {
			log.trace("Iterating through {} realms for PAM authentication", realms.size());
		}
		AuthenticationException authenticationException = null;
		for (Realm realm : realms) {
			aggregate = strategy.beforeAttempt(realm, token, aggregate);
			if (realm.supports(token)) {
				log.trace("Attempting to authenticate token [{}] using realm [{}]", token, realm);
				AuthenticationInfo info = null;
				try {
					info = realm.getAuthenticationInfo(token);
				} catch (AuthenticationException e) {
					authenticationException = e;
					if (log.isDebugEnabled()) {
						String msg = "Realm [" + realm
								+ "] threw an exception during a multi-realm authentication attempt:";
						log.debug(msg, e);
					}
				}
				aggregate = strategy.afterAttempt(realm, token, info, aggregate, authenticationException);
			} else {
				log.debug("Realm [{}] does not support token {}.  Skipping realm.", realm, token);
			}
		}
		if (authenticationException != null) {
			throw authenticationException;
		}
		aggregate = strategy.afterAllAttempts(token, aggregate);
		return aggregate;
	}
}

重写之后替换掉ShiroConfig.java中的身份认证就好了

	/**
	 * 初始化Authenticator 认证器 身份认证
	 */
	@Bean
	public Authenticator authenticator() {
		MultiRealmAuthenticator authenticator = new MultiRealmAuthenticator();
		// 设置两个Realm,一个用于用户登录验证和访问权限获取;一个用于jwt token的认证
		authenticator.setRealms(Arrays.asList(jwtShiroRealm(), dbShiroRealm()));
		// 设置多个realm认证策略,一个成功即跳过其它的
		authenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy());
		return authenticator;
	}

大叔说,坑再多,不在怕,爬起来,反正还会掉坑里的

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值