springboot整合shiro和JWT 使用指北


shiro是什么?

Shiro 是一个强大的简单易用的 Java 安全框架,主要用来更便捷的 认证,授权,加密,会话管理Shiro 首要的和最重要的目标就是容易使用并且容易理解,通过 Shiro 易于理解的API,您可以快速、轻松地获得任何应用程序——从最小的移动应用程序最大的网络和企业应用程序。

shiro的功能

  1. Authentication:身份认证、登录,验证用户是不是拥有相应的身份;
  2. Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限,即判断用户能否进行什么操作,如:验证某个用户是否拥有某个角色,或者细粒度的验证某个用户对某个资源是否具有某个权限!
  3. Session Manager:会话管理,即用户登录后就是第一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通的JavaSE环境,也可以是Web环境;
  4. Cryptography:加密,保护数据的安全性,如密码加密存储到数据库中,而不是明文存储;
  5. Web Support: Web支持,可以非常容易的集成到Web环境;
  6. Caching:缓存,比如用户登录后,其用户信息,拥有的角色、权限不必每次去查,这样可以提高效率
  7. Concurrency: Shiro支持多线程应用的并发验证,即,如在一个线程中开启另一个线程,能把权限自动的传播过去Testing:提供测试支持;
  8. Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
  9. Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了

JWT是什么?

JWT :JWT(JSON Web Token)是一种身份认证的方式,JWT 本质上就一段签名的 JSON 格式的数据。由于它是带有签名的,因此接收者便可以验证它的真实性。可以理解为对 token 这一技术提出一套规范,是在 RFC 7519 中提出的。

JWT的组成和优缺点

本文在这里仅简单介绍JWT的组成和优缺点。详细分析可以在相关搜索引擎中搜索。

JWT的组成

一个JWT token是一个字符串,它由头部、载荷、与签名中间有.分割组成;

头部(header):由令牌的类型和正在使用的签名算法组成;

载荷(playload):放置了token的一些基本信息,以帮助接收服务器理解,同时还有用户自定义信息,支持用户信息交换;

签名(Signature):主要是将前面的头部和载荷进行加密。

JWT的优缺点

JWT的优点:

  • 防CSRF:CSRF(Cross Site Request Forgery)跨站伪造请求。使用JWT登录成功后,会存放在本地local storage中;
  • 适合移动端:使用session需要服务端保存一份信息,使用token不需要只要请求携带token,还可跨语言使用;
  • 无状态:token中有身份验证的所有信息,增加了系统的可用性和伸缩性也带了同样的问题(在缺点中会介绍);
  • 单点登录友好:使用session需要我们把所有session信息存在一台电脑上,使用token的话token保存在客户端。

JWT的缺点:

  • 无状态导致在注销、退出登录、修改密码、修改角色||权限等场景下token依旧有效;
  • token续签问题:即token过期后如何动态刷新token。

shiro + JWT 使用

JWTtoken 令牌

`import org.apache.shiro.authc.AuthenticationToken;

public class JwtToken implements AuthenticationToken{

private String token;

public JwtToken(String jwt){

this.token = jwt;

}

@Override

public Object getCredentials() {

return token;

}

@Override

public Object getPrincipal() {

return token;

}

}`

jwtutils JWT工具类主要是生成token设置过期时间

`import java.util.Date;

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

import org.springframework.stereotype.Component;

import io.jsonwebtoken.Claims;

import io.jsonwebtoken.Jwts;

import io.jsonwebtoken.SignatureAlgorithm;

import lombok.Data;

import lombok.extern.slf4j.Slf4j;

@Slf4j

@Data

@Component

@ConfigurationProperties(prefix = "blogspringback.jwt")

public class JwtUtils {

private static String secret;

private long expire;

private String header;


/**

* @description: 生成token

*** @param {long} userId

*** @return {JWT token}

* @Author: 丑牛

*/

public String generateToken(long userId){

// 过期时间

Date dateNow = new Date();

Date dateExpire = new Date(dateNow.getTime() + expire * 1000);

return Jwts.builder()

.setHeaderParam("type", "JWT")

.setSubject(userId + "")

.setIssuedAt(dateNow)

.setExpiration(dateExpire)

.signWith(SignatureAlgorithm.HS512, secret)

.compact();

}

public static Claims getClaimsByToken(String token){

try {

return Jwts.parser()

.setSigningKey(secret)

.parseClaimsJws(token)

.getBody();

} catch (Exception e) {

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

return null;

}

}

/**

* @description: 判断token是否过期

*** @param {Date} expeiration

*** @return true:过期;false:未过期

* @Author: 丑牛

*/

public boolean isTokenExpired(Date expeiration){

return expeiration.before(new Date());

}

}`

JWTFilter 拦截请求

`@Component

public class Jwtfilter extends AuthenticatingFilter {

@Autowired

JwtUtils jwtUtils;

@Override

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

HttpServletRequest request = (HttpServletRequest) servletRequest;

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

if (ObjectUtils.isEmpty(jwt)){

return null;

}

return new JwtToken(jwt);

}

@Override

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

HttpServletRequest request = (HttpServletRequest) servletRequest;

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

if (ObjectUtils.isEmpty(jwt)) {

return true;

} else {

Claims claim = JwtUtils.getClaimsByToken(jwt);

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

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

}

return executeLogin(servletRequest, serverResponse);

}

}

@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);

}

}`

shiro config 设置自定义的filter

`@Configuration

public class ShiroConfig {

@Autowired

Jwtfilter jwtFilter;

@Bean

public SessionManager sessionManager(RedisSessionDAO redisSessionDAO) {

DefaultSessionManager sessionManager = new DefaultSessionManager();

sessionManager.setSessionDAO(redisSessionDAO);

return sessionManager;

}

@Bean

public DefaultWebSecurityManager securityManager(AccountRealm accountRealm, SessionManager sessionManager,

RedisCacheManager redisCacheManager) {

DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(accountRealm);

// inject sessionManager

securityManager.setSessionManager(sessionManager);

// inject redisCacheManager

securityManager.setCacheManager(redisCacheManager);

return securityManager;

}

@Bean

public ShiroFilterChainDefinition shiroFilterChainDefinition() {

DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();

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

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

chainDefinition.addPathDefinitions(filterMap);

return chainDefinition;

}

@Bean("shiroFilterFactoryBean")

public ShiroFilterFactoryBean shiroFilterFactoryBean(org.apache.shiro.mgt.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;

}

}`

shiro工具类 获取当前用户登录信息

`public class ShiroUtil {

public static AccountProfile getProfile(){

return (AccountProfile) SecurityUtils.getSubject().getPrincipal();

}

}`

realm类 实现认证与授权

`@Component

public class AccountRealm extends AuthorizingRealm{

@Autowired

JwtUtils jwtUtils;

@Autowired

UserService userService;

@Override

public boolean supports(AuthenticationToken token) {

return token instanceof JwtToken;

}

@Override

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

return null;

}

@Override

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

JwtToken jwtToken = (JwtToken) token;

String userId = JwtUtils.getClaimsByToken((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());

}


}`


PS 项目git地址https://github.com/hugfeature/spring-example/tree/main/blog-spring-back

上一篇:Qt编写自定义控件60-声音波形图


下一篇:调度系统设计精要