Shiro:权限框架,可在C/S下运行。 shiro是一套权限管理框架,包括认证、授权等,在使用时直接写相应的接口(小而简单的Shiro就足够)
jwt:是一个鉴权生成加密token的一个名称。
oauth2.0 :一种权限实现标准,是一种安全的授权框架,提供了一套详细的授权机制。用户或应用可以通过公开的或私有的设置,使用第三方认证和授权。
Shiro 有三大核心组件,即 Subject、SecurityManager 和 Realm。
2.依赖引入3.Jwt的自定义工具类,javax.servlet javax.servlet-api4.0.1 org.apache.shiro shiro-spring-boot-starter1.8.0 com.auth0 java-jwt3.18.3
主要功能如下:
生成符合Jwt机制的token字符串可以对token字符串进行校验获取token中的用户信息判断token是否过期
package com.lx.shirojwt.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Date;
public class JwtUtil {
//指定一个token过期时间(毫秒)
// private static final long EXPIRE_TIME = 7 * 24 * 60 * 60 * 1000; //7天
private static final long EXPIRE_TIME = 1000 * 60 * 10;
//注意这里的sercet不是密码,而是进行三件套(salt+MD5+1024Hash)处理密码后得到的凭证
//这里为什么要这么做,在controller中进行说明
public static String createJwtToken(String username, String secret) {
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
Algorithm algorithm = Algorithm.HMAC256(secret); //使用密钥进行哈希
// 附带username信息的token
return JWT.create()
.withClaim("username", username)
.withExpiresAt(date) //过期时间
.sign(algorithm); //签名算法
}
public static boolean verifyToken(String token, String username, String secret) {
try {
//根据密钥生成JWT效验器
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm)
.withClaim("username", username)
.build();
//效验TOKEN(其实也就是比较两个token是否相同)
DecodedJWT jwt = verifier.verify(token);
return true;
} catch (Exception exception) {
return false;
}
}
public static String getUsername(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("username").asString();
} catch (JWTDecodeException e) {
return null;
}
}
public static boolean isExpire(String token) {
DecodedJWT jwt = JWT.decode(token);
return jwt.getExpiresAt().getTime() < System.currentTimeMillis();
}
}
4.自定义一个Realm
自定义的Realm需要继承AuthorizingRealm 类,我们需要重写以下两个方法。
doGetAuthenticationInfo() 方法:用来验证当前登录的用户,获取认证信息。
doGetAuthorizationInfo() 方法:为当前登录成功的用户授予权限和分配角色。
package com.lx.shirojwt.config;
import com.lx.shirojwt.iservice.IUserService;
import com.lx.shirojwt.model.JwtToken;
import com.lx.shirojwt.model.RolePermModel;
import com.lx.shirojwt.util.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.CredentialsMatcher;
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.springframework.beans.factory.annotation.Autowired;
import java.util.List;
@Slf4j
public class CustomerRealm extends AuthorizingRealm {
@Autowired(required = false)
IUserService iUserService;
// 设置realm的名称
@Override
public void setName(String name) {
super.setName("customRealm");
}
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JwtToken;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
log.debug("开始授权!");
if (iUserService == null) {
return null;
}
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
List rolePermModelList = iUserService.queryRolePermByName(principalCollection.getPrimaryPrincipal().toString());
rolePermModelList.forEach(row -> {
simpleAuthorizationInfo.addRole(row.getRoleName());
simpleAuthorizationInfo.addStringPermissions(row.getPermList());
});
return simpleAuthorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) {
log.debug("开始认证!");
if (iUserService == null) {
log.warn("IUserService Is Empty");
return null;
}
JwtToken jwtToken = (JwtToken) authenticationToken;
String token = jwtToken.getCredentials().toString();
String username = JwtUtil.getUsername(token);
//toke过期
if (JwtUtil.isExpire(token)) {
throw new ExpiredCredentialsException();
}
//用户不存在(这个在登录时不会进入,只有在token校验时才有可能进入)
String secret = iUserService.queryPasswordByName(username);
if (username == null || secret == null)
throw new UnknownAccountException();
//密码错误(这里获取到password,就是3件套处理后的保存到数据库中的凭证,作为密钥)
if (!JwtUtil.verifyToken(token, username, secret)) {
throw new IncorrectCredentialsException();
}
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
username, token, this.getName());
return simpleAuthenticationInfo;
}
@Override
public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
super.setCredentialsMatcher(credentialsMatcher);
}
}
4.自定义拦截的过滤器
自定义的过滤器需要继承AuthenticatingFilter,并且需要重写onAccessDenied方法,重写的方法必须执行SecurityUtils.getSubject().login才会执行我们CustomerRealm中的认证的方法(doGetAuthenticationInfo)
package com.lx.shirojwt.filters;
import com.lx.shirojwt.model.JwtToken;
import com.lx.shirojwt.util.ShiroConstants;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.util.StringUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class AuthFilter extends AuthenticatingFilter {
@Override
protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
String requestToken = getRequestToken((HttpServletRequest) servletRequest);
JwtToken jwtToken = new JwtToken(requestToken);
return jwtToken;
}
@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().equalsIgnoreCase("OPTIONS")) {
httpServletResponse.setStatus(200);
return false;
}
return super.preHandle(request, response);
}
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
//完成token登入
//1.检查请求头中是否含有token
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String token = getRequestToken(httpServletRequest);
//2. 如果客户端没有携带token,拦下请求
if (null == token || "".equals(token)) {
responseToken(servletResponse, "Token无效(authorization不存在)", ShiroConstants.UNAUTHORIZED);
return false;
}
//3. 如果有,对进行进行token验证
return executeLogin(servletRequest, servletResponse);
}
public boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
JwtToken jwtToken = (JwtToken) createToken(request, response);
try {
SecurityUtils.getSubject().login(jwtToken);
} catch (Exception e) {
if (e.getClass().getName().equalsIgnoreCase(AuthenticationException.class.getName())) {
responseToken(response, "Token无效,您无权访问该接口!", ShiroConstants.UNAUTHORIZED);
} else if (e.getClass().getName().equalsIgnoreCase(UnknownAccountException.class.getName())) {
responseToken(response, "用户名不存在!", ShiroConstants.OK);
} else if (e.getClass().getName().equalsIgnoreCase(IncorrectCredentialsException.class.getName())) {
responseToken(response, "密码错误!", ShiroConstants.OK);
} else if (e.getClass().getName().equalsIgnoreCase(ExpiredCredentialsException.class.getName())) {
responseToken(response, "Token已过期!", ShiroConstants.OK);
} else {
responseToken(response, "其他错误!", ShiroConstants.OK);
}
return false;
}
return true;
}
private String getRequestToken(HttpServletRequest httpRequest) {
//从header中获取token
String token = httpRequest.getHeader("authorization");
//如果header中不存在token,则从参数中获取token
if (StringUtils.isEmpty(token)) {
token = httpRequest.getParameter("authorization");
}
return token;
}
private void responseToken(ServletResponse response, String msg, int status) {
HttpServletResponse httpServletResponse = (HttpServletResponse)response;
httpServletResponse.setStatus(status);
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json; charset=utf-8");
try (PrintWriter out = httpServletResponse.getWriter()) {
out.write(msg);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Filter Chain 定义说明:
1、一个URL可以配置多个 Filter,使用逗号分隔2、当设置多个过滤器时,全部验证通过,才视为通过3、部分过滤器可指定参数,如 perms,roles
Shiro 内置的 FilterChain
| Filter Name | Class |
|---|---|
| anon | org.apache.shiro.web.filter.authc.AnonymousFilter(所有 url 都都可以匿名访问) |
| authc | org.apache.shiro.web.filter.authc.FormAuthenticationFilter(需要认证才能进行访问) |
| authcBasic | org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
| perms | org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter(需要权限访问) |
| port | org.apache.shiro.web.filter.authz.PortFilter |
| rest | org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter |
| roles | org.apache.shiro.web.filter.authz.RolesAuthorizationFilter(需要角色访问) |
| ssl | org.apache.shiro.web.filter.authz.SslFilter |
| user | org.apache.shiro.web.filter.authc.UserFilter |
// /hello/add 必须认证,并且角色admin和addqueryrole,并且是add权限可以访问
urlFilterMap.put("/hello/add", "authc,roles[admin,addqueryrole],perms[add]");
// /hello/query 必须认证,并且角色admin或者addqueryrole,并且是add或者query权限可以访问
// authc,roles,perms是shiro内置的过滤器,roles中括号是并且的关系
// rolesOr和permsOr 是我们定义的过滤
urlFilterMap.put("/hello/query", "authc,rolesOr[admin,addqueryrole],permsOr[add,query]");
自定义角色或关系
package com.lx.shirojwt.filters;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.CollectionUtils;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.util.List;
import java.util.stream.Collectors;
public class CustomRolesOrFilter extends AuthorizationFilter {
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
Subject subject = getSubject(request, response);
String[] rolesArray = (String[]) mappedValue;
List rolesList = CollectionUtils.asSet(rolesArray).stream().collect(Collectors.toList());
for (String role : rolesList) {
if (subject.hasRole(role)) {
return true;
}
}
return false;
}
}
自定义权限或关系过滤器
package com.lx.shirojwt.filters;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.CollectionUtils;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.util.List;
import java.util.stream.Collectors;
public class CustomPermissionsFilter extends AuthorizationFilter {
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
Subject subject = getSubject(request, response);
String[] rolesArray = (String[]) mappedValue;
List permsList = CollectionUtils.asSet(rolesArray).stream().collect(Collectors.toList());
for (String perms : permsList) {
if (subject.isPermitted(perms)) {
return true;
}
}
return false;
}
}
5.Shiro配置
IFilterService 是我自定新建的抽象类,用于上层抽象具体的业务,自定义过滤器和资源权限的注入。
package com.lx.shirojwt.config;
import com.lx.shirojwt.controller.ExceptionController;
import com.lx.shirojwt.controller.TokenController;
import com.lx.shirojwt.filters.AuthFilter;
import com.lx.shirojwt.filters.CustomPermissionsFilter;
import com.lx.shirojwt.filters.CustomRolesOrFilter;
import com.lx.shirojwt.iservice.IFilterService;
import org.apache.shiro.mgt.DefaultSessionStorageevaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.linkedHashMap;
import java.util.Map;
@Configuration
public class ShiroAutoConfig {
@Autowired(required = false)
IFilterService iFilterService;
//1创建shiroFilter 负责拦截请求
@Bean("shiroFilterFactoryBean")
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//给filter设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map filterMap = new HashMap<>();
filterMap.put("jwtfilter", new AuthFilter());
// 角色或关系
filterMap.put("rolesOr", new CustomRolesOrFilter());
// 权限或关系
filterMap.put("permsOr", new CustomPermissionsFilter());
Map temp = iFilterService == null ? null : iFilterService.createFilterMap();
if (temp != null) {
filterMap.putAll(temp);
}
//自定义过滤器
shiroFilterFactoryBean.setFilters(filterMap);
//配置系统受限资源
Map urlFilterMap = new linkedHashMap<>();
urlFilterMap.put("/createtoken", "anon");
Map tempURL = iFilterService == null ? null : iFilterService.createURLFilterMap();
if (tempURL != null) {
urlFilterMap.putAll(tempURL);
}
// 对除了anon的过滤器,全部增加WT的过滤器
urlFilterMap.entrySet().forEach(kv -> {
if (kv.getValue().contains("anon")) {
return;
}
if (!kv.getValue().contains("jwtfilter")) {
kv.setValue("jwtfilter," + kv.getValue());
}
});
// 全部接口使用JWT的过滤器
urlFilterMap.put("
private DefaultSubjectDAO defaultSubjectDAO() {
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageevaluator defaultSessionStorageevaluator = new DefaultSessionStorageevaluator();
defaultSessionStorageevaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageevaluator(defaultSessionStorageevaluator);
return subjectDAO;
}
//3.创建自定义realm
@Bean("realm")
public Realm getRealm() {
CustomerRealm customerRealm = new CustomerRealm();
return customerRealm;
}
@Bean
@ConditionalOnClass(RestController.class)
public TokenController tokenController() {
TokenController tokenController = new TokenController();
return tokenController;
}
@Bean
@ConditionalOnClass(RestController.class)
public ExceptionController exceptionController() {
ExceptionController exceptionController = new ExceptionController();
return exceptionController;
}
}
package com.lx.shirojwt.iservice;
import javax.servlet.Filter;
import java.util.Collections;
import java.util.Map;
public abstract class IFilterService {
public Map createFilterMap() {
return Collections.emptyMap();
}
public Map createURLFilterMap() {
return Collections.emptyMap();
}
}
package com.lx.shirojwt.iservice;
import com.lx.shirojwt.model.RolePermModel;
import java.util.List;
public interface IUserService {
String queryPasswordByName(String userName);
List queryRolePermByName(String userName);
}
6.设置全局异常捕获权限异常
package com.lx.shirojwt.controller;
import com.lx.shirojwt.util.ShiroConstants;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.apache.shiro.authz.AuthorizationException;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
@RestController
@RestControllerAdvice
public class ExceptionController extends MarkController {
@GetMapping("/noAuthorization")
public ResponseEntity noAuthorization(HttpServletRequest request) {
return new ResponseEntity("无权限访问该接口!", HttpStatus.FORBIDDEN);
}
@ExceptionHandler({AuthorizationException.class})
public ResponseEntity authorizationException(Exception exception, HttpServletRequest request) {
return new ResponseEntity("无权限访问!" + request.getRequestURI(), HttpStatus.FORBIDDEN);
}
}
7.启动测试
无token字段:
token过期:
正常token:



