1、shiro 配置
import java.util.HashMap;
import java.util.Map;
import javax.servlet.Filter;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
@Configuration
public class ShiroConfig {
@Bean("securityManager")
public DefaultWebSecurityManager webSecurityManager(MyRealm myRealm) {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
//使用自己的realm
manager.setRealm(myRealm);
//关闭shiro自带的session
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
manager.setSubjectDAO(subjectDAO);
return manager;
}
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
//添加自己的过滤器并取名为jwt
Map filterMap = new HashMap<>();
filterMap.put("jwt", new JWTFilter());
factoryBean.setFilters(filterMap);
factoryBean.setSecurityManager(securityManager);
factoryBean.setUnauthorizedUrl("/401");
//自定义url规则
Map filterRuleMap = new HashMap<>();
filterRuleMap.put("/**", "jwt");
// 跳过对swagger拦截
filterRuleMap.put("/swagger-resources/**", "anon");
filterRuleMap.put("/webjars/**", "anon");
filterRuleMap.put("/v2/**", "anon");
filterRuleMap.put("/swagger-ui.html/**", "anon");
filterRuleMap.put("/login/**", "anon");
filterRuleMap.put("/401", "anon");
factoryBean.setFilterChainDefinitionMap(filterRuleMap);
return factoryBean;
}
/**
* 添加注解支持
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
//强制使用cglib代理
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}
2、登录的Realm
controller只负责封装下参数,然后扔给Shiro了,这时候Shiro收到后,会到所有的realm中找能处理UsernamePasswordToken
的Realm
import com.yzf.enterprise.app.risk.core.utils.JWTUtil;
import com.yzf.enterprise.app.risk.model.entity.OperateUserEntity;
import com.yzf.enterprise.app.risk.service.ActionService;
import com.yzf.enterprise.app.risk.service.UserService;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MyRealm extends AuthorizingRealm {
private static final Logger logger = LoggerFactory.getLogger(MyRealm.class);
@Autowired
private UserService userService;
@Autowired
private ActionService actionService;
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JWTToken;
}
/**
* 当需要检测用户权限的时候才会调用此方法,例如checkRole,checkPermission之类的
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
JWTToken jwtToken = (JWTToken) getAvailablePrincipal(principals);
// 权限认证 无权限就异常处理
if (!actionService.checkUrlAction(jwtToken.getUrl(),
Long.valueOf(JWTUtil.getValue(jwtToken.getToken(), "userId")))) {
throw new AuthenticationException("用户无权限");
}
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
Set set = new HashSet<>();
set.add(jwtToken.getUrl());
simpleAuthorizationInfo.setStringPermissions(set);
return simpleAuthorizationInfo;
}
/**
* 默认使用此方法进行用户名正确与否验证,错误抛出异常即可。
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth)
throws AuthenticationException {
JWTToken jwtToken = (JWTToken) auth;
String token = jwtToken.getToken();
// 解密获得userId,用于和数据库进行对比
String userId = JWTUtil.getValue(token, "userId");
if (StringUtils.isBlank(userId)) {
throw new AuthenticationException("token invalid");
}
OperateUserEntity operateUserEntity = userService.selectByKey(Long.valueOf(userId));
if (operateUserEntity == null) {
throw new AuthenticationException("用户不存在,userId=" + userId);
}
// 验证token 是否正确
if (!JWTUtil.verify(token, "userId", userId, operateUserEntity.getPassword())) {
throw new AuthenticationException("用户信息异常");
}
return new SimpleAuthenticationInfo(jwtToken, jwtToken, this.getName());
}
}
3、设置 自己的filter
import com.alibaba.fastjson.JSON;
import com.yunzhangfang.common.utils.AjaxResult;
import com.yzf.enterprise.app.risk.base.constant.GlobalProperties;
import com.yzf.enterprise.app.risk.core.constant.RiskConstants.OperateShiro;
import com.yzf.enterprise.app.risk.core.enums.ResultCodeEnum;
import com.yzf.enterprise.app.risk.core.support.Json;
import java.io.IOException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;
public class JWTFilter extends BasicHttpAuthenticationFilter {
private static final Logger logger = LoggerFactory.getLogger(JWTFilter.class);
/**
* 判断用户是否想要登入
*/
@Override
protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
return true;
}
/**
* 验证登陆
*/
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response)
throws Exception {
JWTToken jwtToken = analyzeJWTToken(request);
// 提交给realm进行登入,如果错误会抛出异常并被捕获
getSubject(request, response).login(jwtToken);
// 如果没有抛出异常则代表登入成功
return true;
}
/**
* 权限认证
*/
@Override
protected boolean isPermissive(Object mappedValue) {
return SecurityUtils.getSubject().isPermitted(String.valueOf(mappedValue));
}
/**
* 是否允许继续访问
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response,
Object mappedValue) {
// 跳过开发环境
if ("dev".equals(GlobalProperties.profiles) || "test-local".equals(GlobalProperties.profiles)) {
return true;
}
try {
// 验证登录
executeLogin(request, response);
// 验证权限
isPermissive(analyzeJWTToken(request).getUrl());
return true;
} catch (Exception e) {
logger.error("token验证异常:" + e, e);
return false;
}
}
/**
* 如果Shiro Login认证成功,会进入该方法,等同于用户名密码登录成功,我们这里还判断了是否要刷新Token
*/
@Override
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
ServletRequest request, ServletResponse response) throws Exception {
issueSuccessRedirect(request, response);
return true;
}
/**
* 拒绝访问之后 是否需要继续处理
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse res, Object mappedValue)
throws IOException {
logger.info("token认证失败");
AjaxResult ajaxResult = AjaxResult.failed(ResultCodeEnum.UNLOGIG);
HttpServletResponse response = (HttpServletResponse) res;
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(JSON.toJSONString(ajaxResult));
return false;
}
/**
* 对跨域提供支持
*/
@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"));
// 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
httpServletResponse.setStatus(HttpStatus.OK.value());
return false;
}
return super.preHandle(request, response);
}
/**
* 非法请求路由到/401
*/
private void response401(ServletRequest req, ServletResponse resp) {
try {
HttpServletResponse response = (HttpServletResponse) resp;
AjaxResult ajaxResult = AjaxResult.failed(ResultCodeEnum.UNAUTH.code(),
ResultCodeEnum.UNAUTH.message());
response.getWriter().write(Json.toJsonString(ajaxResult));
} catch (Exception e) {
logger.error("response401异常", e);
}
}
/**
* 解析cookie 获取token
*/
private JWTToken analyzeJWTToken(ServletRequest request) {
HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
JWTToken jwtToken = new JWTToken();
jwtToken.setUrl(httpServletRequest.getRequestURI());
Cookie[] cookies = httpServletRequest.getCookies();
if (cookies == null || cookies.length <= 0) {
throw new AuthenticationException("cookie为null");
}
for (Cookie cookie : cookies) {
if (OperateShiro.TOKENNAME.equals(cookie.getName())) {
jwtToken.setToken(cookie.getValue());
return jwtToken;
}
}
throw new AuthenticationException("未查询到cookieName=" + OperateShiro.TOKENNAME);
}
}
4、JWT token封装
import org.apache.shiro.authc.AuthenticationToken;
public class JWTToken implements AuthenticationToken {
/**
* token
*/
private String token;
/**
* 请求url
*/
private String url;
public JWTToken() {
}
public JWTToken(String token) {
this.token = token;
}
public JWTToken(String token, String url) {
this.token = token;
this.url = url;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
@Override
public Object getPrincipal() {
return this;
}
@Override
public Object getCredentials() {
return this;
}
}
5、登录接口 将token 写入cookie
@Override
public void login(String username, String password, HttpServletResponse response) {
if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
throw new RiskBusinessException("用户或者密码不能为空");
}
Example example = new Example(OperateUserEntity.class);
Criteria criteria = example.createCriteria();
criteria.andEqualTo("username", username);
password = CommonUtil.md5(password);
criteria.andEqualTo("password", password);
OperateUserEntity operateUserEntity = userService.selectOneByExample(example);
Preconditions.checkArgument(operateUserEntity != null, "账号或者密码错误");
Preconditions.checkArgument(operateUserEntity.getStatus() != 0, "用户未启用");
String token = JWTUtil.sign("userId", String.valueOf(operateUserEntity.getId()), password);
// 认证成功写入cookie
Cookie cookie = new Cookie(OperateShiro.TOKENNAME, token);
cookie.setDomain(GlobalOpetateProperties.domain);
cookie.setPath("/");
// 设置有效期12小时(单位:秒)
cookie.setMaxAge(12 * 60 * 60);
response.addCookie(cookie);
}
5、整体的流程为
分享:https://www.jianshu.com/p/0b1131be7ace