1、创建token实体
@Data
public class SysUserTokenEntity {
//用户ID
private Long userId;
//token
private String token;
//过期时间
private Date expireTime;
//更新时间
private Date updateTime;
}
2、配置jwtUtils
@ConfigurationProperties(prefix = "conf.jwt")
@Component
public class JwtUtils {
private Logger logger = LoggerFactory.getLogger(getClass());
private String secret;
private long expire;
private String header;
/**
* 生成jwt token
*/
public String generateToken(long userId) {
Date nowDate = new Date();
//过期时间
Date expireDate = new Date(nowDate.getTime() + expire * 1000);
return Jwts.builder()
.setHeaderParam("typ", "JWT")
.setSubject(userId+"")
.setIssuedAt(nowDate)
.setExpiration(expireDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public Claims getClaimByToken(String token) {
try {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}catch (Exception e){
return null;
}
}
/**
* token是否过期
* @return true:过期
*/
public boolean isTokenExpired(Date expiration) {
return expiration.before(new Date());
}
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
public long getExpire() {
return expire;
}
public void setExpire(long expire) {
this.expire = expire;
}
public String getHeader() {
return header;
}
public void setHeader(String header) {
this.header = header;
}
}
在配置文件中设置jwt配置
conf:
jwt:
# 加密秘钥
secret: f4e2e52034348f86b67cde581c0f9eb5
# token有效时长,7天,单位秒
expire: 604800
header: token
2、自定义过滤器,继承AuthenticatingFilter类
- 重写
createToken
方法,返回一个AuthenticationToken
接口的实现类。
@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);
}
AuthenticationToken接口的实现类
public class OAuth2Token implements AuthenticationToken {
private String token;
public OAuth2Token(String token){
this.token = token;
}
@Override
public String getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token;
}
}
- 重写
onAccessDenied
与isAccessAllowed
方法,这两个方法如果都返回false,则整个filter控制链都将结束。
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
//获取请求token,如果token不存在,直接返回401
String token = getRequestToken((HttpServletRequest) request);
if (StringUtils.isBlank(token)) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
// 设置是否将响应暴露给页面
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
// 响应头指定了该响应的资源是否被允许与给定的origin共享
httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());
// String json = new Json().toJson(R.error(HttpStatus.HTTP_UNAUTHORIZED, "invalid token"));
String json = new ObjectMapper().writeValueAsString(R.error(HttpStatus.HTTP_UNAUTHORIZED, "invalid token"));
httpResponse.getWriter().print(json);
return false;
}
return executeLogin(request, response);
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
// 若方法是OPTIONS类型,则直接通过
if (((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())) {
return true;
}
return false;
}
- 重写
onLoginFailure
方法,此方法将会在登陆(认证)失败时调用,在登陆失败后返回登录失败错误信息。
// 登录失败时调用此方法
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setContentType("application/json;charset=utf-8");
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());
try {
//返回登录失败的异常信息
Throwable throwable = e.getCause() == null ? e : e.getCause();
R r = R.error(HttpStatus.HTTP_UNAUTHORIZED, throwable.getMessage());
String json = new ObjectMapper().writeValueAsString(r);
httpResponse.getWriter().print(json);
} catch (IOException e1) {
}
return false;
}
3、提供Realm,在 Shiro 中,最终是通过 Realm 来获取应用程序中的用户、角色及权限信息的。通常情况下,在 Realm 中会直接从我们的数据源中获取 Shiro 需要的验证信息。可以说,Realm 是专用于安全框架的 DAO。
- 重写
supports
方法,提供我们自己的token类,否则会出现如下错误
{
“msg”: “Realm [com.tianqicode.framework.shiro.OAuth2Realm@4de52950] does not support authentication token [com.tianqicode.framework.shiro.OAuth2Token@c7ad1fc]. Please ensure that the appropriate Realm implementation is configured correctly or that the realm accepts AuthenticationTokens of this type.”,
“code”: 401
}
// 重写supports方法添加自定义token类型
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof OAuth2Token;
}
-
重写
doGetAuthenticationInfo
方法,此方法的功能- 检查提交的进行认证的令牌信息
- 根据令牌信息从数据源(通常为数据库)中获取用户信息
- 对用户信息进行匹配验证。
- 验证通过将返回一个封装了用户信息的
AuthenticationInfo
实例。 - 验证失败则抛出
AuthenticationException
异常信息。
/**
* 认证时调用,处理令牌信息是否符合规则
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String accessToken = (String) token.getPrincipal();
//根据accessToken,查询用户信息
SysUserTokenEntity tokenEntity = shiroService.queryByToken(accessToken);
//token失效
Claims claims = null;
if(tokenEntity != null){
claims = jwtUtils.getClaimByToken(tokenEntity.getToken());
}
if(claims == null || jwtUtils.isTokenExpired(claims.getExpiration())){
throw new IncorrectCredentialsException("token失效,请重新登录");
}
//查询用户信息
SysUserEntity user = shiroService.queryUser(tokenEntity.getUserId());
//账号锁定
if(user.getStatus() == 0){
throw new LockedAccountException("账号已被锁定,请联系管理员");
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, accessToken, getName());
return info;
}
- 重写
doGetAuthorizationInfo
方法,检验用户是否有相应的接口权限
/**
* 授权(验证权限时调用)
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SysUserEntity user = (SysUserEntity)principals.getPrimaryPrincipal();
Long userId = user.getUserId();
//用户权限列表
Set<String> permsSet = shiroService.getUserPermissions(userId);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(permsSet);
return info;
}
4、编写shiro配置类,ShiroConfig
@Configuration
public class ShiroConfig {
/**
* shiro session的管理
*/
@Bean("sessionManager")
public SessionManager sessionManager(){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
// 定时清理失效会话开关
sessionManager.setSessionValidationSchedulerEnabled(true);
// SessionIdCookie启用开关
sessionManager.setSessionIdCookieEnabled(true);
return sessionManager;
}
@Bean("securityManager")
public SecurityManager securityManager(OAuth2Realm oAuth2Realm, SessionManager sessionManager) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置自定义Realm
securityManager.setRealm(oAuth2Realm);
securityManager.setSessionManager(sessionManager);
return securityManager;
}
// 配置shiro过滤器
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
//oauth过滤
Map<String, Filter> filters = new HashMap<>();
filters.put("oauth2", new OAuth2Filter());
shiroFilter.setFilters(filters);
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/**", "oauth2");
shiroFilter.setFilterChainDefinitionMap(filterMap);
return shiroFilter;
}
@Bean("lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
proxyCreator.setProxyTargetClass(true);
return proxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}