欢迎大家来到IT世界,在知识的湖畔探索吧!
企业级的应用权限控制一直是重中之重,Apache Shiro作为一款轻量级的权限控制框架,一直深受广大开发者的喜爱。公司最近新开始的一个项目,也正在使用shiro作为权限框架。
首先给给大家看看我们的项目的shiro配置
shiro
- shiro公共配置
/*shiroFilter*/
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean ();
//Shiro的核心安全接口,这个属性是必须的
shiroFilterFactoryBean.setSecurityManager (securityManager);
Map<String, Filter> filterMap = new LinkedHashMap<> ();
filterMap.put ("jwtFilter", jwtFilter ());
filterMap.put ("urlFilter", urlFilter ());
//filterMap.put ("systemFilter", systemFilter ());
shiroFilterFactoryBean.setFilters (filterMap);
/*定义shiro过滤链 Map结构
* Map中key(xml中是指value值)的第一个'/'代表的路径是相对于HttpServletRequest.getContextPath()的值来的
* anon:它对应的过滤器里面是空的,什么都没做,这里.do和.jsp后面的*表示参数,比方说login.jsp?main这种
* authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter
*/
/* 过滤链定义,从上向下顺序执行,一般将 / ** 放在最为下边:这是一个坑呢,一不小心代码就不好使了;
authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问 */
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<> ();
filterChainDefinitionMap.put ("/swagger-ui.html", "anon");
filterChainDefinitionMap.put ("/swagger", "anon");
filterChainDefinitionMap.put ("/swagger-resources/**/**", "anon");
filterChainDefinitionMap.put ("/v2/**", "anon");
filterChainDefinitionMap.put ("/webjars/**", "anon");
filterChainDefinitionMap.put ("/doc.html", "anon");
filterChainDefinitionMap.put ("/", "anon");
filterChainDefinitionMap.put ("/static/**", "anon");
filterChainDefinitionMap.put ("/mass/login", "anon");
filterChainDefinitionMap.put ("/system/login", "anon");
filterChainDefinitionMap.put ("/login/auth", "anon");
filterChainDefinitionMap.put ("/login/logout", "anon");
filterChainDefinitionMap.put ("/user/logout", "anon");
filterChainDefinitionMap.put ("/error", "anon");
filterChainDefinitionMap.put ("/**", "jwtFilter,urlFilter");
shiroFilterFactoryBean.setFilterChainDefinitionMap (filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/*自定义filter*/
@Bean
public UrlFilter urlFilter() {
return new UrlFilter ();
}
@Bean
public JwtFilter jwtFilter() {
return new JwtFilter ();
}
/*SecurityManager*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager ();
//securityManager.setRealm (userRealm ());
securityManager.setRealm (jwtRealm ());
return securityManager;
}
/*Realm*/
@Bean
public JwtRealm jwtRealm() {
return new JwtRealm ();
}
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor ();
}
/**
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
* 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
*/
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator ();
advisorAutoProxyCreator.setProxyTargetClass (true);
return advisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor ();
authorizationAttributeSourceAdvisor.setSecurityManager (securityManager ());
return authorizationAttributeSourceAdvisor;
}
欢迎大家来到IT世界,在知识的湖畔探索吧!
JwtToken
JwtRealm
欢迎大家来到IT世界,在知识的湖畔探索吧!public class JwtRealm extends AuthorizingRealm {
@Autowired
JWTTokenUtil jwtTokenUtil;
@Autowired
MassUserMapper massUserMapper;
@Autowired
SysUserMapper sysUserMapper;
@Autowired
RedisService redisService;
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JwtToken;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return new SimpleAuthorizationInfo();
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
if(!(token instanceof JwtToken)){
throw new AuthenticationException("没有对应的realm");
}
JwtToken jwtToken = (JwtToken) token;
try {
JwtUserVO jwtUser = jwtTokenUtil.jwtUser(jwtToken.getPrincipal().toString());
if(ObjectUtils.isEmpty(jwtUser)){
throw new AuthenticationException("认证过期,请重新登录!");
}
LoginUserVO loginUser = redisService.get(RedisKeyEnum.JWT_TOKEN_KEY.key() + token.getPrincipal(), LoginUserVO.class);
if(ObjectUtils.isEmpty(loginUser)){
loginUser = this.validateUser(jwtUser.getUserId(), jwtUser.getUserType());
redisService.set (RedisKeyEnum.JWT_TOKEN_KEY.key() + token.getPrincipal(), loginUser, 2L, TimeUnit.HOURS);
}
return new SimpleAuthenticationInfo(loginUser, jwtToken.getPrincipal().toString(), getName());
}catch (Exception e){
throw new AuthenticationException("认证过期,请重新登录!");
}
}
public LoginUserVO validateUser(String userId, Integer userType){
if(UserTypeEnum.MASS_USER.getUserType() == userType){
return this.validateMassUser(userId);
}else if(UserTypeEnum.ADMIN_USER.getUserType() == userType){
return this.validateSysUser(userId);
}else{
throw new AccountException("不支持的用户类型!");
}
}
/**
* 通过userId验证mass用户
* @param userId
* @return
*/
public LoginUserVO validateMassUser(String userId){
MassUserEntity massUserEntity = massUserMapper.selectById(userId);
if(ObjectUtils.isEmpty(massUserEntity)){
throw new AccountException("账号不存在!");
}
if(massUserEntity.getDeleted() == 1){
throw new AccountException("用户账号或密码错误!");
}
return new LoginUserVO(massUserEntity.getId(), massUserEntity.getUsername(),
massUserEntity.getPassword(), UserTypeEnum.MASS_USER.getUserType());
}
/**
* 通过userId验证sys用户
* @param userId
* @return
*/
public LoginUserVO validateSysUser(String userId){
SysUserEntity sysUserEntity = sysUserMapper.selectById(userId);
if(ObjectUtils.isEmpty(sysUserEntity)){
throw new AccountException("账号不存在!");
}
if(sysUserEntity.getDeleted() == 1){
throw new AccountException("用户账号或密码错误!");
}
return new LoginUserVO(sysUserEntity.getId(), sysUserEntity.getUsername(),
sysUserEntity.getPassword(), UserTypeEnum.ADMIN_USER.getUserType());
}
}
UrlFilter
public class UrlFilter extends AccessControlFilter {
private static Logger logger = LoggerFactory.getLogger (UrlFilter.class);
@Autowired
private RedisService redisService;
@Value("${system.access.admin-url}")
private String adminUrl;
@Value("${system.access.deal-url}")
private String dealUrl;
@Value("${system.access.mass-url}")
private String massUrl;
@Autowired
JWTTokenUtil jwtTokenUtil;
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
try {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String token = httpServletRequest.getHeader ("token");
LoginUserVO loginUser = redisService.get (RedisKeyEnum.JWT_TOKEN_KEY.key () + token, LoginUserVO.class);
if (ObjectUtils.isEmpty (loginUser)) {
return false;
}
// 2.根据用户类型,验证是否有权访问此路径
if (loginUser.getUserType () == UserTypeEnum.ADMIN_USER.getUserType ()) {
return true;
}
//委办局用户可以访问委办局的url
if (loginUser.getUserType () == UserTypeEnum.DEAL_USER.getUserType ()) {
return pathsMatch (dealUrl, request) || pathsMatch (adminUrl, request);
}
//民众用户可以访问
if (loginUser.getUserType () == UserTypeEnum.MASS_USER.getUserType ()) {
return pathsMatch (massUrl, request);
}
return true;
} catch (Exception e) {
logger.error ("访问拦截出错!", e);
}
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
PrintWriter out = null;
try {
response.setContentType ("application/json");
response.setCharacterEncoding ("utf-8");
out = response.getWriter ();
String result = JSON.toJSONString (request.getAttribute ("resultJSON"));
out.write (JSON.toJSONString (Response.error ("没有权限访问该路径")));
} catch (IOException e) {
throw e;
} finally {
if (null != out) {
out.flush ();
out.close ();
}
}
return false;
}
}
JwtFilter
欢迎大家来到IT世界,在知识的湖畔探索吧!public class JwtFilter extends BasicHttpAuthenticationFilter {
private static Logger logger = LoggerFactory.getLogger (JwtFilter.class);
private static String tokenKey = "token";
@Autowired
JWTTokenUtil jwtTokenUtil;
@Override
protected boolean isLoginAttempt(ServletRequest servletRequest, ServletResponse servletResponse) {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String jwtToken = request.getHeader (tokenKey);
if (StringUtils.isNotEmpty (jwtToken)) {
return true;
}
return false;
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
if (isLoginAttempt (request, response)) {
try {
this.executeLogin (request, response);
return true;
} catch (Exception e) {
logger.error ("用户登录失败!");
ajaxNotLogin (response, e.getMessage ());
}
}
ajaxNotLogin (response, "请登录系统.");
return false;
}
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String jwtToken = httpServletRequest.getHeader (tokenKey);
Subject subject = getSubject (request, response);
subject.login (new JwtToken (jwtToken));
if (subject.isAuthenticated ()) {
return true;
} else {
return false;
}
}
public void ajaxNotLogin(ServletResponse servletResponse, String msg) {
HttpServletResponse response = WebUtils.toHttp (servletResponse);
response.setStatus (HttpStatus.UNAUTHORIZED.value ());
response.setCharacterEncoding ("UTF-8");
response.setContentType ("application/json; charset=utf-8");
Response<String> result = Response.error (msg);
PrintWriter out = null;
try {
out = response.getWriter ();
out.write (JSON.toJSONString (result));
} catch (IOException e) {
logger.error ("response输出错误", e);
} finally {
if (null != out) {
out.flush ();
out.close ();
}
}
}
}
以上就是我们项目关于shiro的一些配置。
一号坑
创建filterChainDefinitionMap时一定要使用LinkedHashMap,否则在使用过程中会有意想不到的问题。因为Shiro的过滤器链是根据配置的filterChainDefinitionMap中配置的路由拦截规则,在调用时是根据map中元素的顺序依次来调用,而HashMap是无序的,因此,在使用时使用有序的LinkedHashMap
二号坑
先给大家看一个现象吧,我们的swagger竟然被拦截了。
我明明在上面配置了swagger的访问不需要鉴权
filterChainDefinitionMap.put (“/swagger-ui.html”, “anon”);
filterChainDefinitionMap.put (“/swagger”, “anon”);
filterChainDefinitionMap.put (“/swagger-resources/**/**”, “anon”);
filterChainDefinitionMap.put (“/v2/**”, “anon”);
filterChainDefinitionMap.put (“/webjars/**”, “anon”);
filterChainDefinitionMap.put (“/doc.html”, “anon”);
为什么还是被拦截了呢。
查看项目启动日志后发现了下面这段话
可以看到我们编写的Shiro的Filter被放进了Tomcat的过滤器链,也就是被ApplicationFilterChain管理起来了。
因此出现这个问题就不难理解了,我们在shiro中配置了不需要验证,但是在实际运行的时候,Tomcat中的过滤器还是拦截了我们的请求。
因此我们自定义的shiro过滤器一定不能在springboot启动的时候被注入独立注入进tomcat内。
现在问题的关键就是我们如何抑制自定义的shiroFiler向tomcat的拦截器链注入。
这里我们需要用到一个特殊的Bean——FilterRegistrationBean
具体的代码如下
协商如上的代码后,再启动项目可以看到已经可以正常使用。
总结
上面就是我们项目在使用shiro的时候碰到的一些问题,希望可以帮助你解决项目中出现的同样问题。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/11829.html