12 min to read
Shiro
学习Shiro
官方文档地址
- https://shiro.apache.org/spring-boot.html
demo地址
- 后续补上
版本说明
- spring boot : 2.1.1.RELEASE
- spring cloud: Greenwich.M3
- spring shiro: 1.4.0-RC2
基本框架
- spring boot
- spring mvc
- mybatis
- shiro
说明
其他的东西我就不说了,网上一大堆.
这个例子主要针对 api 保护,后台只提供api,不提供页面访问
这个例子中没有指定登录,你需要自己写登录,就按照你自己的业务写即可,
只要token能查询到就可以,如果需要可以在配置上加入login
保证这些代码可以100%运行,只有一些service需要根据你自己的业务逻辑定义
这里就不给出了
顺便把一些坑给填了
创建项目
用idea创建一个spring boot 项目,这里就不多说了
shiro maven
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.4.0-RC2</version>
</dependency>
创建类
1.OAuth2Token
package com.web.authorizing;
import org.apache.shiro.authc.AuthenticationToken;
public class OAuth2Token implements AuthenticationToken {
private static final long serialVersionUID = -1586550416883518120L;
private String token;
public OAuth2Token(String token) {
this.token = token;
}
@Override
public String getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token;
}
}
2.UserRealm
package com.web.authorizing;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.Set;
/**
* 认证身份
*/
@Component
public class UserRealm extends AuthorizingRealm {
/**
* 这里导入自己的service
* 需要 用户的service token的service 权限的service 或者角色的service
*/
@Autowired
private UserBiz userBiz;
@Autowired
private TokenBiz tokenBiz;
/**
* 菜单的service 这里是权限映射菜单
*/
@Autowired
private MenuBiz menuBiz;
/**
* 判断是否能处理AuthenticationToken
*/
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof OAuth2Token;
}
/**
* 初始化
*/
@Autowired
public UserRealm(CacheManager cacheManager) {
//配置缓存
super(cacheManager);
//开启身份验证缓存
this.setAuthenticationCachingEnabled(true);
//开启授权缓存
this.setAuthorizationCachingEnabled(true);
}
/**
* 身份验证
* 用来身份验证的
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//获取请求到的token,这个token在AuthenticatingFilter中封装的
String token = authenticationToken.getPrincipal().toString();
if (token == null) {
return null;
}
//通过token获取用户信息,下面根据自己的业务进行处理
SysUserToken userToken = tokenBiz.queryByToken(token);
//不合格抛出异常
if (userToken == null || userToken.getMobile() && userToken.getExpireTime().getTime() < System.currentTimeMillis()) {
throw new IncorrectCredentialsException("r.error.token.failure");
} else {
tokenBiz.updateExampleTiemByUserId(userToken.getUserId(), getExampleTime());
}
//判断用户是否锁定
SysUser user = userBiz.queryUserStatusAndUserIdByUserId(userToken.getUserId());
if (user.getStatus() == 0) {
throw new LockedAccountException("r.error.user.lock");
}
//返回SimpleAuthenticationInfo(用户对象,token(验证的凭证),getName())
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(token, token, getName());
SecurityUtils.getSubject().getSession().setAttribute(token,user);
return authenticationInfo;
}
/**
* 授权
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取用户对象
SysUser sysUser = (SysUser) principalCollection.getPrimaryPrincipal();
//在数据库查询权限
Set<String> permsSet = menuBiz.queryPermsByDelFlageAndUserId(sysUser.getUserId());
if (permsSet.size() == 0) {
return null;
}
//配置权限,也可以配置角色simpleAuthorizationInfo.setRoles()
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setStringPermissions(permsSet);
return simpleAuthorizationInfo;
}
//这个可以不用管,这是自己的业务需要
private Date getExampleTime() {
return new Date(System.currentTimeMillis() + UserRealm.EXAMPLE_TIME);
}
}
3.OAuth2Filter
package com.web.authorizing;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.web.util.R;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.http.HttpStatus;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* oauth2过滤器
*/
@Slf4j
public class OAuth2Filter extends AuthenticatingFilter {
//使用的jackson
private ObjectMapper om;
//这个可以不用管,用来做国际化的
private MessageSource messageSource;
//可以把这个构造方法删除
public OAuth2Filter(ObjectMapper om, MessageSource messageSource) {
this.om = om;
this.messageSource = messageSource;
}
/**
* 创建token
*/
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
//获取请求中token
String token = getRequestToken((HttpServletRequest) request);
if (StringUtils.isBlank(token)) {
return null;
}
return new OAuth2Token(token);
}
/**
*表示是否允许访问
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
return false;
}
/**
* 表示当访问拒绝时是否已经处理了
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
//获取token
String token = getRequestToken((HttpServletRequest) request);
if (StringUtils.isBlank(token)) {
AuthenticationException exception = new AuthenticationException("r.error.token.null");
onLoginFailure(null,exception,request,response);
return false;
}
return executeLogin(request, response);
}
/**
* 登录失败则调用
*/
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setContentType("application/json;charset=utf-8");
try {
//处理登录失败的异常
Throwable throwable = e.getCause() == null ? e : e.getCause();
var locale = LocaleContextHolder.getLocale();
String message = messageSource.getMessage(throwable.getMessage(), null, locale);
//这个R对象可以自定义,我这里只是继承了HaseMap
R r = R.error(HttpStatus.UNAUTHORIZED.value(), message);
httpResponse.getWriter().print(om.writeValueAsString(r));
} catch (IOException e1) {
e1.printStackTrace();
}
return false;
}
/**
* 获取请求的token
* 从header中获取token
* 如果header中不存在token,则从参数中获取token
*/
private String getRequestToken(HttpServletRequest httpRequest) {
String token = httpRequest.getHeader("token");
if (StringUtils.isBlank(token)) {
token = httpRequest.getParameter("token");
}
return token;
}
}
5.ShiroConfig
package com.web.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Maps;
import com.llvision.web.authorizing.OAuth2Filter;
import com.llvision.web.authorizing.UserRealm;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.Map;
@Configuration
public class ShiroConfig {
/**
* session 管理
*
* @return
*/
@Bean
public SessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionValidationSchedulerEnabled(true);
sessionManager.setSessionIdUrlRewritingEnabled(false);
return sessionManager;
}
/**
* 授权管理
*
* @param userRealm
* @param sessionManager
* @return
*/
@Bean
public DefaultWebSecurityManager securityManager(UserRealm userRealm, SessionManager sessionManager,CacheManager cacheManager) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);
securityManager.setSessionManager(sessionManager);
//这一步比较重要,不然不能使用SecurityUtils相关的静态方法
SecurityUtils.setSecurityManager(securityManager);
return securityManager;
}
/**
* shiro 自带缓存
*/
@Bean
protected CacheManager cacheManager(){
return new MemoryConstrainedCacheManager();
}
/**
* shiro的Filter 这里只要是空的就行,和ShiroFilterFactoryBean选择一个就行
* 这个不能添加Filter
*/
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition(){
return new DefaultShiroFilterChainDefinition();
}
/**
* shiro工厂bean
* filterMap
* anon:所有url都都可以匿名访问
* authc: 需要认证才能进行访问
* user:配置记住我或认证通过可以访问
*
* @param securityManager
* @return
*/
@Bean("shiroFilterFactoryBean")
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager, ObjectMapper om, MessageSource messageSource) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
OAuth2Filter auth2Filter = new OAuth2Filter(om, messageSource);
shiroFilter.setSecurityManager(securityManager);
Map<String, Filter> filters = Maps.newHashMap();
filters.put("oauth2", auth2Filter);
shiroFilter.setFilters(filters);
Map<String, String> filterMap = Maps.newLinkedHashMap();
//需要能够访问登录接口
filterMap.put("/login","anon")
filterMap.put("/**", "perms");
//你自己的拦截器一定要放在最后,这个filter还不能交给spring管理
filterMap.put("/**", "oauth2");
shiroFilter.setFilterChainDefinitionMap(filterMap);
return shiroFilter;
}
/**
* 管理shiro Bean的生命周期
* @return
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 配置代理
*/
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
proxyCreator.setProxyTargetClass(true);
return proxyCreator;
}
}
6.
package com.web.api;
import com.llvision.web.entity.SysUser;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
/**
* 登录接口,这里的业务逻辑按照自己的业务逻辑写就行
* 但是token要能查询到
*/
@PostMapping("login")
public String login(@RequestBody SysUser user) {
return "token";
}
/**
* 这里可以直接使用shiro相关的注解
* 这个使用的事权限注解,更多注解需要自己查询
*/
@RequiresPermissions("userInfo:add")
@PostMapping("test")
public String test() {
Session subject = SecurityUtils.getSubject().getSession();
SysUser user = (SysUser) subject.getAttribute(SecurityUtils.getSubject().getPrincipal().toString());
return "ok";
}
/**
* 退出登录
*/
@PostMapping("logout")
public String test() {
//添加自己的业务逻辑
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "ok";
}
}