之前我有整合springboot-jwt这种(参见https://blog.csdn.net/qq_14926283/article/details/110653829) 今天就在它这基础上来整合shiro
Apache Shiro 是一个强大易用的 Java 安全框架,提供了认证、授权、加密和会话管理等功能,对于任何一个应用程序,Shiro 都可以提供全面的安全管理服务。并且相对于其他安全框架,Shiro 要简单的多。
Apache Shiro 是 Java 的一个安全框架,对比 Spring Security,可能没有 Spring Security 做的功能强大,但是实际用到的我们没有这么复杂的那么多的东西,所以我们用到shiro就比较多。
Authentication:身份认证 / 登录,验证用户是不是拥有相应的身份;
Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
Session Management:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的;
Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
Web Support:Web 支持,可以非常容易的集成到 Web 环境;
Caching:缓存,比如用户登录后,其用户信息、拥有的角色 / 权限不必每次去查,这样可以提高效率;
Concurrency:shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
记住一点,Shiro 不会去维护用户、维护权限;这些需要我们自己去设计 / 提供;然后通过相应的接口注入给 Shiro 即可。
接下来我们分别从外部和内部来看看 Shiro 的架构,对于一个好的框架,从外部来看应该具有非常简单易于使用的 API,且 API 契约明确;从内部来看的话,其应该有一个可扩展的架构,即非常容易插入用户自定义实现,因为任何框架都不能满足所有需求。
好了我们之间以最简单的形式来集成一下
由于是基于之前的demo改的,我这里就只把关键的代码贴出来
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.3.2</version>
</dependency>
我这边只多引入了这一个jar坐标
package com.zkb.filter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import com.zkb.shiro.JwtToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class JwtFilter extends BasicHttpAuthenticationFilter{
private Logger logger = LoggerFactory.getLogger(JwtFilter.class);
/**
* 校验token
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
HttpServletRequest req = (HttpServletRequest) request;
String authorization = req.getHeader("token");
logger.info("isToken: "+authorization);
if(authorization != null){
Subject subject = getSubject(request, response);
subject.login(new JwtToken(authorization));
return true;
}
return false;
}
}
package com.zkb.shiro;
import org.apache.shiro.authc.AuthenticationToken;
public class JwtToken implements AuthenticationToken {
private static final long serialVersionUID = 1L;
private String token;
public JwtToken(String token) {
this.token = token;
}
@Override
public Object getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token;
}
}
package com.zkb.shiro;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.Filter;
import com.zkb.filter.JwtFilter;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.session.mgt.DefaultSessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.web.filter.DelegatingFilterProxy;
@Configuration
public class ShiroConfig {
//管理shiro一些bean的生命周期,生命周期就是初始化与销毁的管理
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
//自动创建代理类
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
daap.setProxyTargetClass(true);
return daap;
}
//自定义realm
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public ShiroRealm shiroRealm() {
ShiroRealm shiroRealm = new ShiroRealm();
return shiroRealm;
}
//会话管理器
@Bean
public DefaultWebSecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//获取securityManager的SubjectDao的实现类
DefaultSubjectDAO subjectDAO = (DefaultSubjectDAO)securityManager.getSubjectDAO();
//获取subjectDao的SessionStorageEvaluator的实现类
DefaultSessionStorageEvaluator sessionStorageEvaluator = (DefaultSessionStorageEvaluator)subjectDAO.getSessionStorageEvaluator();
//禁用session的存储策略
sessionStorageEvaluator.setSessionStorageEnabled(false);
securityManager.setRealm(this.shiroRealm());
return securityManager;
}
//会话管理类 禁用session
@Bean
public DefaultSessionManager defaultSessionManager(){
DefaultSessionManager manager = new DefaultSessionManager();
manager.setSessionValidationSchedulerEnabled(false);
return manager;
}
//不注入此实例,shiro注解将不生效
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
aasa.setSecurityManager(securityManager);
return aasa;
}
//shiro过滤器
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean factory(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
shiroFilter.setLoginUrl("/unauthenticated");
shiroFilter.setUnauthorizedUrl("/unauthorized");
Map<String, Filter> filterMap = new HashMap<>();
filterMap.put("jwt", new JwtFilter());
shiroFilter.setFilters(filterMap);
Map<String, String> filterRuleMap = new HashMap<>();
filterRuleMap.put("/user/login", "anon");
filterRuleMap.put("/doc.html", "anon");
filterRuleMap.put("/webjars/**", "anon");
filterRuleMap.put("/ace/**", "anon");
filterRuleMap.put("/swagger-resources/**", "anon");
filterRuleMap.put("/v2/**", "anon");
filterRuleMap.put("/**", "jwt");
shiroFilter.setFilterChainDefinitionMap(filterRuleMap);
return shiroFilter;
}
//解决UnavailableSecurityManagerException
@Bean
public FilterRegistrationBean delegatingFilterProxy(){
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
DelegatingFilterProxy proxy = new DelegatingFilterProxy();
proxy.setTargetFilterLifecycle(true);
proxy.setTargetBeanName("shiroFilter");
filterRegistrationBean.setFilter(proxy);
return filterRegistrationBean;
}
}
package com.zkb.shiro;
import com.zkb.model.User;
import com.zkb.service.TokenService;
import com.zkb.service.UserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
/**
* 自定义realm
* @author 芒果味的小可乐
*
*/
public class ShiroRealm extends AuthorizingRealm {
@Autowired
private TokenService tokenService;
@Autowired
UserService userService;
/**
* 使用jwt代替原生token
*
* @param token
* @return
*/
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JwtToken;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String credentials = (String)token.getCredentials();
String username = tokenService.getUsername(credentials);
User userAth = new User();
userAth.setUsername(username);
User user = userService.findByUsername(userAth);
if(user == null) {
throw new UnknownAccountException("账号不存在");
}
if(!tokenService.verify(credentials, user.getUsername(), user.getPassword())) {
throw new AuthenticationException("用户名/密码错误");
}
return new SimpleAuthenticationInfo(credentials, credentials, getName());
}
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
// TODO Auto-generated method stub
return null;
}
}
这里就OK了
run起来 输入127.0.0.1:8081/doc.html 发现是没有被拦截的吧
如果不设置token的话
这个没过滤的地址可以发现是访问不了的,直接就报错了
不带token或token无效是不让去访问这个接口的
把用户登录之后的token填进去
接口就能正常访问了
当然这里只是写了一个非常简单的demo来了解怎么用的
具体权限当然不可能这么简单,会涉及到用户,角色,角色权限(菜单)是动态的,可以给用户分配不同的角色,每个角色又又不同的菜单,控制的非常细的话可以到按钮到数据
参见:
anon:例子/admins/**=anon 没有参数,表示可以匿名使用。
authc:例如/admins/user/**=authc表示需要认证(登录)才能使用,没有参数
roles(角色):例子/admins/user/**=roles[admin],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如admins/user/**=roles["admin,guest"],每个参数通过才算通过,相当于hasAllRoles()方法。
perms(权限):例子/admins/user/**=perms[user:add:*],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。
rest:例子/admins/user/**=rest[user],根据请求的方法,相当于/admins/user/**=perms[user:method] ,其中method为post,get,delete等。
port:例子/admins/user/**=port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString
是你访问的url里的?后面的参数。
authcBasic:例如/admins/user/**=authcBasic没有参数表示httpBasic认证
ssl:例子/admins/user/**=ssl没有参数,表示安全的url请求,协议为https
user:例如/admins/user/**=user没有参数表示必须存在用户,当登入操作时不做检查
demo参见:https://download.csdn.net/download/qq_14926283/13606161