shiro简介
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
引用至百度百科
最近弄一个新的服务,比较简单,整合了一下安全框架
springboot+jwt+shiro还是比较常见的安全框架整合,简单记录一下这次整合过程
首先,pom.xml 文件添加依赖
<!--整合Shiro安全框架-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>${shiro.version}</version>
<exclusions>
<exclusion>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
<!--集成jwt实现token认证-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.2.0</version>
</dependency>
配置Shiro
@Configuration
public class ShiroConfiguration {
/**
* ehcache缓存方案
*
* @return
*/
@Bean
public CacheManager shiroCacheManager() {
EhCacheManager cacheManager = new EhCacheManager();
cacheManager.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
return cacheManager;
}
@Bean
public JWTTokenRealm jwtTokenRealm() {
JWTTokenRealm jwtTokenRealm = new JWTTokenRealm();
// jwtTokenRealm.setCredentialsMatcher(credentialsMatcher());
jwtTokenRealm.setCacheManager(shiroCacheManager());
return jwtTokenRealm;
}
@Bean
public Authenticator authenticator() {
ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
List<Realm> realms = new ArrayList<>();
realms.add(jwtTokenRealm());
authenticator.setRealms(realms);
return authenticator;
}
/**
* 安全管理配置
*
* @return
*/
@Bean
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(jwtTokenRealm());
securityManager.setCacheManager(shiroCacheManager());
// securityManager.setSessionManager(getSessionManage());
// 关闭shiro自带的session
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
securityManager.setSubjectDAO(subjectDAO);
SecurityUtils.setSecurityManager(securityManager);
return securityManager;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
*
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor =
new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* 配置shiro的拦截器链工厂,默认会拦截所有请求
*
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
// 配置安全管理
filterFactoryBean.setSecurityManager(securityManager);
Map<String, Filter> filterMap = new LinkedHashMap<>();
// filterMap.put("authc", new AjaxPermissionsAuthorizationFilter());
filterMap.put("jwt", new JWTFilter());
// filterMap.put("kickout", kickoutSessionControlFilter());
filterFactoryBean.setFilters(filterMap);
// 配置拦截地址和拦截器
// 必须使用LinkedHashMap,因为拦截有先后顺序
// authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 登录接口不设置权限认证
filterChainDefinitionMap.put("/login", "anon");
//swagger2免拦截
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
filterChainDefinitionMap.put("/v2/**", "anon");
filterChainDefinitionMap.put("/swagger-resources/**", "anon");
filterChainDefinitionMap.put("/webjars/**", "anon");
// 登出不需要认证
filterChainDefinitionMap.put("/logout", "anon");
// 不需要认证的接口配置
filterChainDefinitionMap.put("/app/**", "anon");
// 剩下的其他资源地址全部需要用户认证后才能访问
filterChainDefinitionMap.put("/**", "jwt");
filterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return filterFactoryBean;
}
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator getAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
creator.setProxyTargetClass(true);
return creator;
}
}
配置文件
ehcache-shiro.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" name="shirocache">
<diskStore path="java.io.tmpdir"/>
<defaultCache
maxElementsInMemory="10000"
maxElementsOnDisk="0"
eternal="true"
overflowToDisk="true"
diskPersistent="false"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
diskSpoolBufferSizeMB="50"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LFU"
/>
<!-- 登录记录缓存 锁定10分钟 -->
<cache name="passwordRetryCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>
<cache name="authorizationCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>
<cache name="authenticationCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>
<cache name="shiro-activeSessionCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>
<cache name="shiro-kickout-session"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>
</ehcache>
yml文件配置
#token过期时间 (3 * 60 * 60 * 1000)
tokenExpireTime: 10800000
jwt集成相关类
/**
* jwt过滤器 实现token鉴权
*/
public class JWTFilter extends BasicHttpAuthenticationFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(JWTFilter.class);
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
// 获取Authorization字段
String token = httpServletRequest.getHeader("Authorization");
if (token != null) {
String username = JWTUtil.getUsername(token);
if (username == null) {
responseOnAccessDenied(request, response);
return false;
}
JWTToken jwtToken = new JWTToken(username, token);
try {
getSubject(request, response).login(jwtToken);
} catch (Exception e) {
responseOnAccessDenied(request, response);
return false;
}
return true;
}
responseOnAccessDenied(request, response);
return false;
}
private void responseOnAccessDenied(ServletRequest request, ServletResponse response) {
LOGGER.warn("未经授权访问[{}]被拦截.", ((HttpServletRequest) request).getRequestURL());
String responseJson = "{\"code\":401,\"msg\":\"Unauthorized.\"}";
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json");
httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
try (PrintWriter out = response.getWriter()) {
out.print(responseJson);
} catch (IOException e) {
LOGGER.error("获取PrintWriter输出流失败,失败原因", e);
}
}
/**
* 对跨域提供支持
*/
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
httpServletResponse.setHeader("Access-Control-Allow-Headers",
httpServletRequest.getHeader("Access-Control-Request-Headers"));
httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
// 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
httpServletResponse.setStatus(HttpStatus.OK.value());
return false;
}
return super.preHandle(request, response);
}
@Bean
public FilterRegistrationBean registration(JWTFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}
}
public class JWTToken implements AuthenticationToken {
/**
* 用户名
*/
private String username;
/**
* 令牌
*/
private String token;
public JWTToken(String username, String token) {
this.username = username;
this.token = token;
}
@Override
public Object getPrincipal() {
return username;
}
@Override
public Object getCredentials() {
return token;
}
}
/**
* 基于jWT自定义的Realm
*
*/
public class JWTTokenRealm extends AuthorizingRealm {
private static final Logger LOGGER = LoggerFactory.getLogger(JWTTokenRealm.class);
@Resource
private AdminService adminService;
/**
* Convenience implementation that returns
* <tt>getAuthenticationTokenClass().isAssignableFrom( token.getClass() );</tt>. Can be overridden
* by subclasses for more complex token checking.
* <p>Most configurations will only need to set a different class via
* {@link #setAuthenticationTokenClass}, as opposed to overriding this method.
*
* @param token the token being submitted for authentication.
* @return true if this authentication realm can process the submitted token instance of the class, false otherwise.
*/
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JWTToken;
}
/**
* 认证信息(身份验证,用于用户登录)
*
* @param authenticationToken 用来验证用户身份
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
throws AuthenticationException {
String username = (String) authenticationToken.getPrincipal();
AdminEntity adminEntity = adminService.query().eq("user_name", username).one();
// 设置部分参数为null
// tdUser.setPartner(null);
// adminEntity.setTdRoleList(null);
String token = (String) authenticationToken.getCredentials();
// 校验token
checkToken(token, username, adminEntity.getPassword());
return new SimpleAuthenticationInfo(adminEntity, token, getName());
}
/**
* 校验token
*
* @param token 令牌
* @param username 用户名
* @param password 加密后的密码
*/
private void checkToken(String token, String username, String password) {
if (!JWTUtil.verify(token, username, password)) {
throw new AcException();
}
}
/**
* 更新权限等信息时清除缓存
*/
public void clearCached() {
PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
super.clearCache(principals);
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
}
登录接口方法内进行调用,返回token给前端
String token = JWTUtil.sign(adminEntity.getUserName(), adminEntity.getPassword());
之后其他没有免拦截的接口请求时,在请求头加入token即可