springboot结合jwt实现权限认证
1.什么是JWT
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
2.优点
体积小、传输快
支持跨域授权,因为跨域无法共享cookie
分布式系统中,很好地解决了单点登录问题
3.使用场景
1. 认证,这是比较常见的使用场景,只要用户登录过一次系统,之后的请求都会包含签名出来的token,通过token也可以用来实现单点登录。
2. 交换信息,通过使用密钥对来安全的传送信息,可以知道发送者是谁、放置消息被篡改。
4.项目克隆地址(以下过程代码简写,可自行克隆代码查看实现过程,这个框架是我自己搭建的,里边已经集成了很多东西,喜欢的可以研究研究,都是比较常用的技术,有什么问题也可以评论,看到会回复,账号密码 admin admin)
ssh://admin@121.36.107.248:29418/goudan.git
5.集成过程
(1)导入sql表,我把sql文件放在下图所示位置
sql文件(2)添加依赖
依赖(3)主体有5个文件需要添加,分别是shiroConfig、OAuth2Filer配置、OAuth2Realm、OAuth2Token、TokenGenerator
3.1 shiroConfig
shiroConfig具体代码如下
/**
* @program: xiaowu
* @description: Shiro配置
* @author: Wu
* @create: 2020-08-29 09:46
**/
@Configuration
public class ShiroConfig {
@Bean("sessionManager")
public SessionManagersessionManager(){
DefaultWebSessionManager sessionManager =new DefaultWebSessionManager();
sessionManager.setSessionValidationSchedulerEnabled(true);
sessionManager.setSessionIdCookieEnabled(true);
return sessionManager;
}
@Bean("securityManager")
public SecurityManagersecurityManager(OAuth2Realm oAuth2Realm, SessionManager sessionManager) {
DefaultWebSecurityManager securityManager =new DefaultWebSecurityManager();
securityManager.setRealm(oAuth2Realm);
securityManager.setSessionManager(sessionManager);
return securityManager;
}
@Bean("shiroFilter")
public ShiroFilterFactoryBeanshirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter =new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
//oauth过滤
Map filters =new HashMap<>();
filters.put("oauth2", new OAuth2Filter());
shiroFilter.setFilters(filters);
Map filterMap =new LinkedHashMap<>();
filterMap.put("/druid/**", "anon");
filterMap.put("/app/**", "anon");
filterMap.put("/login", "anon");
filterMap.put("/**", "oauth2");
shiroFilter.setFilterChainDefinitionMap(filterMap);
return shiroFilter;
}
@Bean("lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessorlifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public DefaultAdvisorAutoProxyCreatordefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator proxyCreator =new DefaultAdvisorAutoProxyCreator();
proxyCreator.setProxyTargetClass(true);
return proxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisorauthorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor =new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}
3.2 OAuth2Filer 过滤器(oauth2是一种规范,springcloud有个springsecurity oauth2是实现,这里用的jwt)
OAuth2Filer具体代码如下
/**
* @program: xiaowu
* @description: oauth2过滤器
* @author: Wu
* @create: 2020-08-29 10:09
**/
public class OAuth2Filterextends AuthenticatingFilter {
@Override
protected AuthenticationTokencreateToken(ServletRequest request, ServletResponse response)throws Exception {
//获取请求token
String token = getRequestToken((HttpServletRequest) request);
if(StringUtil.isBlank(token)){
return null;
}
return new OAuth2Token(token);
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
if(((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())){
return true;
}
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response)throws Exception {
//获取请求token,如果token不存在,直接返回401
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String token = getRequestToken((HttpServletRequest) request);
if(StringUtil.isBlank(token)){
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Allow-Origin", httpServletRequest.getHeader("Origin"));
JSONObject json =new JSONObject();
json.put("code", "401");
json.put("msg", "invalid token");
httpResponse.getWriter().print(json);
return false;
}
return executeLogin(request, response);
}
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
httpResponse.setContentType("application/json;charset=utf-8");
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Allow-Origin", httpServletRequest.getHeader("Origin"));
try {
//处理登录失败的异常
Throwable throwable = e.getCause() ==null ? e : e.getCause();
JSONObject json =new JSONObject();
json.put("code", "401");
json.put("msg", throwable.getMessage());
httpResponse.getWriter().print(json);
}catch (IOException e1) {
}
return false;
}
/**
* 获取请求的token
*/
private StringgetRequestToken(HttpServletRequest httpRequest){
//从header中获取token
String token = httpRequest.getHeader("token");
//如果header中不存在token,则从参数中获取token
if(StringUtil.isBlank(token)){
token = httpRequest.getParameter("token");
}
return token;
}
}
3.3 OAuth2Realm配置(这个里面可以设置角色、权限和认证信息)
OAuth2Realm具体代码如下
/**
* @program: xiaowu
* @description: 认证
* @author: Wu
* @create: 2020-08-29 10:18
**/
@Component
public class OAuth2Realmextends AuthorizingRealm {
@Autowired
private ManagerServicemanagerService;
@Override
public boolean supports(AuthenticationToken token) {
return tokeninstanceof OAuth2Token;
}
/**
* 授权(验证权限时调用, 控制role 和 permissins时使用)
*/
@Override
protected AuthorizationInfodoGetAuthorizationInfo(PrincipalCollection principals) {
ManagerInfo manager = (ManagerInfo)principals.getPrimaryPrincipal();
Integer managerId = manager.getManagerId();
SimpleAuthorizationInfo info =new SimpleAuthorizationInfo();
// 模拟权限和角色
Set permsSet =new HashSet<>();
Set roles =new HashSet<>();
if (managerId ==1) {
// 超级管理员-权限
permsSet.add("delete");
permsSet.add("update");
permsSet.add("view");
roles.add("admin");
}else {
// 普通管理员-权限
permsSet.add("view");
roles.add("test");
}
info.setStringPermissions(permsSet);
info.setRoles(roles);
return info;
}
/**
* 认证(登录时调用)
*/
@Override
protected AuthenticationInfodoGetAuthenticationInfo(AuthenticationToken token)throws AuthenticationException {
String accessToken = (String) token.getPrincipal();
//根据accessToken,查询用户信息
ManagerToken managerToken =managerService.queryByToken(accessToken);
//token失效
SimpleDateFormat sm =new SimpleDateFormat("yyyyMMddHHmmss");
Date expireTime;
boolean flag =true;
try {
expireTime = sm.parse(managerToken.getExpireTime());
flag = managerToken ==null || expireTime.getTime() < System.currentTimeMillis();
}catch (ParseException e) {
e.printStackTrace();
}
if(flag){
throw new IncorrectCredentialsException("token失效,请重新登录");
}
//查询用户信息
ManagerInfo managerInfo =managerService.getManagerInfo(managerToken.managerId);
//账号锁定
// if(managerInfo.getStatus() == 0){
// throw new LockedAccountException("账号已被锁定,请联系管理员");
// }
SimpleAuthenticationInfo info =new SimpleAuthenticationInfo(managerInfo, accessToken, getName());
return info;
}
}
3.4 OAuth2Token设置
OAuth2Token3.5 TokenGenerator, 生成token
TokenGenerator具体代码
/**
* @program: xiaowu
* @description: 生成token
* @author: Wu
* @create: 2020-08-29 10:26
**/
public class TokenGenerator {
public static StringgenerateValue() {
return generateValue(UUID.randomUUID().toString());
}
private static final char[]hexCode ="0123456789abcdef".toCharArray();
public static StringtoHexString(byte[] data) {
if (data ==null) {
return null;
}
StringBuilder r =new StringBuilder(data.length *2);
for (byte b : data) {
r.append(hexCode[(b >>4) &0xF]);
r.append(hexCode[(b &0xF)]);
}
return r.toString();
}
public static StringgenerateValue(String param) {
try {
MessageDigest algorithm = MessageDigest.getInstance("MD5");
algorithm.reset();
algorithm.update(param.getBytes());
byte[] messageDigest = algorithm.digest();
return toHexString(messageDigest);
}catch (Exception e) {
throw new RuntimeException("生成Token失败", e);
}
}
}
4.测试(这里以登录接口验证为例)
登录接口实现层代码
实现层mapper文件还有这种写法(头一次见,以前都是在业务代码里写的)
xml文件postman测试
测试 mysql接口权限已经完成,剩下的就是根据过期时间的业务代码,当然这个token加密算法比较简单,我们可以更换加密算法,这以后再完善
喜欢请关注