shiro、jwt、redis整合

database

shiro、jwt、redis、SpringBoot整合

shiro、jwt、redis整合

一、步骤

1.导入坐标

<!--redis-->

<dependency>

<groupId>org.crazycake</groupId>

<artifactId>shiro-redis-spring-boot-starter</artifactId>

<version>3.2.1</version>

</dependency>

<!-- jwt -->

<dependency>

<groupId>io.jsonwebtoken</groupId>

<artifactId>jjwt</artifactId>

<version>0.9.1</version>

</dependency>

2.编写ShiroConfig

目的:实现session共享

session共享

HTTP协议(1.1)是无状态的,所以服务器在需要识别用户访问的时候,就要做相应的记录用于跟踪用户操作,这个实现机制就是Session。当一个用户第一次访问服务器的时候,服务器就会为用户创建一个Session,每个Session都有一个唯一的SessionId(应用级别)用于标识用户。

因此要使用shiro继承redis

使用redis管理session能够方便的实现session集群,并且在服务重启(或服务器重启)时不会丢失session。

package com.gyb.config;

import com.gyb.shiro.AccountRealm;

import com.gyb.shiro.JwtFilter;

import org.apache.shiro.mgt.SecurityManager;

import org.apache.shiro.session.mgt.SessionManager;

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.crazycake.shiro.RedisCacheManager;

import org.crazycake.shiro.RedisSessionDAO;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;

import java.util.HashMap;

import java.util.LinkedHashMap;

import java.util.Map;

@Configuration

public class ShiroConfig {

@Autowired

JwtFilter jwtFilter;

@Bean

public SessionManager sessionManager(RedisSessionDAO redisSessionDAO) {

DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();

sessionManager.setSessionDAO(redisSessionDAO);

return sessionManager;

}

@Bean

public DefaultWebSecurityManager securityManager(AccountRealm accountRealm,

SessionManager sessionManager,

RedisCacheManager redisCacheManager) {

DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(accountRealm);

securityManager.setSessionManager(sessionManager);

securityManager.setCacheManager(redisCacheManager);

return securityManager;

}

@Bean

public ShiroFilterChainDefinition shiroFilterChainDefinition() {

DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();

Map<String, String> filterMap = new LinkedHashMap<>();

//将所有的过滤交给自定义的jwt来做

filterMap.put("/**", "jwt");

chainDefinition.addPathDefinitions(filterMap);

return chainDefinition;

}

@Bean("shiroFilterFactoryBean")

public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,

ShiroFilterChainDefinition shiroFilterChainDefinition) {

ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();

shiroFilter.setSecurityManager(securityManager);

Map<String, Filter> filters = new HashMap<>();

filters.put("jwt", jwtFilter);

shiroFilter.setFilters(filters);

Map<String, String> filterMap = shiroFilterChainDefinition.getFilterChainMap();

shiroFilter.setFilterChainDefinitionMap(filterMap);

return shiroFilter;

}

}

3.解决跨域问题

(后面还要在过滤器解决: 因为过滤器在拦截器之前执行,可能直接被过滤掉)

步骤分析

spring初始化时加载corsMapping的配置信息并注册到实现于IOC容器,具体实例以及信息封装:RequestMappingHandlerMapping -> corsConfigurationSource -> corsConfigurations

接口访问时,在DispatchServlet中doDispatch方法里,通过getHandler拿到HandlerExecutionChain,cors配置信息封装在HandlerExecutionChain实例中,在getHandler方法中的this.handlerMappings集合中可以看到之前注册的RequestMappingHandlerMapping

package com.gyb.config;

import org.springframework.context.annotation.Configuration;

import org.springframework.web.servlet.config.annotation.CorsRegistry;

import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**

* 解决跨域问题

*/

@Configuration

public class CorsConfig implements WebMvcConfigurer {

@Override

public void addCorsMappings(CorsRegistry registry) {

registry.addMapping("/**")

.allowedOriginPatterns("*")

.allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")

// 允许发送Cookie

.allowCredentials(true)

.maxAge(3600)

.allowedHeaders("*");

}

}

4.创建shiro包

在包中创建

AccountProfile: 重写doGetAuthenticationInfo认证方法时,返回的第一个参数

AccountRealm: 自定义域类(主要是实现认证和授权两个方法)

*** JwtFifter:** 1.将请求中的token存储,(使用JwtToken类)

​ 2.判断token是否已经失效,没失效则执行executeLogin,然后一步步走到自己编写的域类里实现的两个方法

​ 3.重写登录失败方法,将错误信息返回 :Result result = Result.fail(throwable.getMessage());

​ 4.解决跨域问题

JwtToken:将request域中Authorization的value值封装。

4.1 创建AccountProfile类

package com.gyb.shiro;

import lombok.Data;

import java.io.Serializable;

@Data

public class AccountProfile implements Serializable {

private Long id;

private String username;

private String avatar;

private String email;

}

4.2 创建AccountRealm类

package com.gyb.shiro;

import cn.hutool.core.bean.BeanUtil;

import com.gyb.entity.User;

import com.gyb.service.UserService;

import com.gyb.util.JwtUtils;

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;

import org.springframework.stereotype.Component;

@Component

public class AccountRealm extends AuthorizingRealm {

@Autowired

JwtUtils jwtUtils;

@Autowired

UserService userService;

@Override

public boolean supports(AuthenticationToken token) {

return token instanceof JwtToken;

}

/**

* 授权

* @param principals

* @return

*/

@Override

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

return null;

}

/**

* 认证

* @param token

* @return

* @throws AuthenticationException

*/

@Override

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

JwtToken jwtToken = (JwtToken) token;

String userId = jwtUtils.getClaimByToken((String) jwtToken.getPrincipal()).getSubject();

User user = userService.getById(Long.valueOf(userId));

if (user == null) {

throw new UnknownAccountException("账户不存在");

}

if (user.getStatus() == -1) {

throw new LockedAccountException("账户已被锁定");

}

AccountProfile profile = new AccountProfile();

BeanUtil.copyProperties(user, profile);

return new SimpleAuthenticationInfo(profile, jwtToken.getCredentials(), getName());

}

}

4.3 创建JwtFifter类

package com.gyb.shiro;

import cn.hutool.json.JSONUtil;

import com.gyb.common.lang.Result;

import com.gyb.util.JwtUtils;

import io.jsonwebtoken.Claims;

import org.apache.shiro.authc.AuthenticationException;

import org.apache.shiro.authc.AuthenticationToken;

import org.apache.shiro.authc.ExpiredCredentialsException;

import org.apache.shiro.web.filter.authc.AuthenticatingFilter;

import org.apache.shiro.web.util.WebUtils;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;

import org.springframework.util.StringUtils;

import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import java.io.IOException;

@Component

public class JwtFilter extends AuthenticatingFilter {

@Autowired

JwtUtils jwtUtils;

@Override

protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {

HttpServletRequest request = (HttpServletRequest) servletRequest;

String jwt = request.getHeader("Authorization");

if(!StringUtils.hasLength(jwt) ) {

return null;

}

return new JwtToken(jwt);

}

@Override

protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {

HttpServletRequest request = (HttpServletRequest) servletRequest;

String jwt = request.getHeader("Authorization");

if(!StringUtils.hasLength(jwt)) {

return true;

} else {

// 校验jwt

Claims claim = jwtUtils.getClaimByToken(jwt);

if(claim == null || jwtUtils.isTokenExpired(claim.getExpiration())) {

throw new ExpiredCredentialsException("token已失效,请重新登录");

}

// 执行登录

return executeLogin(servletRequest, servletResponse);

}

}

@Override

protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {

HttpServletResponse httpServletResponse = (HttpServletResponse) response;

Throwable throwable = e.getCause() == null ? e : e.getCause();

Result result = Result.fail(throwable.getMessage());

String json = JSONUtil.toJsonStr(result);

try {

httpServletResponse.getWriter().print(json);

} catch (IOException ioException) {

}

return false;

}

@Override

protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {

HttpServletRequest httpServletRequest = WebUtils.toHttp(request);

HttpServletResponse httpServletResponse = WebUtils.toHttp(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"));

// 跨域时会首先发送一个OPTIONS请求,这里我们给OPTIONS请求直接返回正常状态

if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {

httpServletResponse.setStatus(org.springframework.http.HttpStatus.OK.value());

return false;

}

return super.preHandle(request, response);

}

}

4.4 创建JwtToken类

package com.gyb.shiro;

import org.apache.shiro.authc.AuthenticationToken;

public class JwtToken implements AuthenticationToken {

private String token;

public JwtToken(String jwt) {

this.token = jwt;

}

@Override

public Object getPrincipal() {

return token;

}

@Override

public Object getCredentials() {

return token;

}

}

5.创建util包

JwtUtil: 1.在用户调用登录接口时,生成jwt

​ 2.获取token的Claims对象的方法,Claims大家可以理解为jwt明文的报文体结构,在验证的时候可以通过获取其到当前请求的用 户payload信息。

​ 3.判定token是否过期

ShiroUtil: 只有一行代码,我是为了简便= =,(也可以不写此类)

​ 用于获取当前用户的Principal

5.1 JwtUtil

package com.gyb.util;

import io.jsonwebtoken.Claims;

import io.jsonwebtoken.Jwts;

import io.jsonwebtoken.SignatureAlgorithm;

import lombok.Data;

import lombok.extern.slf4j.Slf4j;

import org.springframework.boot.context.properties.ConfigurationProperties;

import org.springframework.stereotype.Component;

import java.util.Date;

/**

* jwt工具类

*/

@Slf4j

@Data

@Component

@ConfigurationProperties(prefix = "gyb.jwt")

public class JwtUtils {

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){

log.debug("validate is token error ", e);

return null;

}

}

/**

* token是否过期

* @return true:过期

*/

public boolean isTokenExpired(Date expiration) {

return expiration.before(new Date());

}

}

5.2 ShiroUtil

package com.gyb.util;import com.gyb.shiro.AccountProfile;import org.apache.shiro.SecurityUtils;public class ShiroUtil {    public static AccountProfile getProfile() {        return (AccountProfile) SecurityUtils.getSubject().getPrincipal();    }}

以上是 shiro、jwt、redis整合 的全部内容, 来源链接: utcz.com/z/535964.html

回到顶部