shiro是什么?
Shiro
是一个强大的简单易用的 Java
安全框架,主要用来更便捷的 认证,授权,加密,会话管理
。Shiro
首要的和最重要的目标就是容易使用并且容易理解,通过 Shiro
易于理解的API
,您可以快速、轻松地获得任何应用程序——从最小的移动应用程序最大的网络和企业应用程序。
shiro的功能
- Authentication:身份认证、登录,验证用户是不是拥有相应的身份;
- Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限,即判断用户能否进行什么操作,如:验证某个用户是否拥有某个角色,或者细粒度的验证某个用户对某个资源是否具有某个权限!
- Session Manager:会话管理,即用户登录后就是第一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通的JavaSE环境,也可以是Web环境;
- Cryptography:加密,保护数据的安全性,如密码加密存储到数据库中,而不是明文存储;
- Web Support: Web支持,可以非常容易的集成到Web环境;
- Caching:缓存,比如用户登录后,其用户信息,拥有的角色、权限不必每次去查,这样可以提高效率
- Concurrency: Shiro支持多线程应用的并发验证,即,如在一个线程中开启另一个线程,能把权限自动的传播过去Testing:提供测试支持;
- Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
- 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