javascript
json web token没有哪个成分_【分享项目】给你看看我们公司的登录认证是怎么做的?!(SpringBoot+Shiro+Token+Redis)...
背景交代
以前項(xiàng)目中權(quán)限認(rèn)證沒(méi)有使用安全框架,都是在自定義filter中判斷是否登錄以及用戶(hù)是否有操作權(quán)限的。最近開(kāi)了新項(xiàng)目,搭架子時(shí),想到使用安全框架來(lái)解決認(rèn)證問(wèn)題,spring security太過(guò)龐大,我們的項(xiàng)目不大,所以決定采用Shiro。
什么是Shiro
Apache Shiro 是一個(gè)強(qiáng)大靈活的開(kāi)源安全框架,可以完全處理身份驗(yàn)證、授權(quán)、加密和會(huì)話管理。
Realm是Shiro的核心組建,也一樣是兩步走,認(rèn)證和授權(quán),在Realm中的表現(xiàn)為以下兩個(gè)方法。
- 認(rèn)證:doGetAuthenticationInfo,核心作用判斷登錄信息是否正確
- 授權(quán):doGetAuthorizationInfo,核心作用是獲取用戶(hù)的權(quán)限字符串,用于后續(xù)的判斷
Shiro過(guò)濾器
當(dāng) Shiro 被運(yùn)用到 web 項(xiàng)目時(shí),Shiro 會(huì)自動(dòng)創(chuàng)建一些默認(rèn)的過(guò)濾器對(duì)客戶(hù)端請(qǐng)求進(jìn)行過(guò)濾。以下是 Shiro 提供的部分過(guò)濾器:
| anon | 表示可以匿名使用 |
| authc | 表示需要認(rèn)證(登錄)才能使用 |
| authcBasic | 表示httpBasic認(rèn)證 |
| perms | 當(dāng)有多個(gè)參數(shù)時(shí)必須每個(gè)參數(shù)都通過(guò)才通過(guò) perms[“user:add:”] |
| port | port[8081] 跳轉(zhuǎn)到schemal://serverName:8081?queryString |
| rest | 權(quán)限 |
| roles | 角色 |
| ssl | 表示安全的url請(qǐng)求 |
| user | 表示必須存在用戶(hù),當(dāng)?shù)侨氩僮鲿r(shí)不做檢查 |
為什么選擇shiro
- 簡(jiǎn)單性,Shiro 在使用上較 Spring Security 更簡(jiǎn)單,更容易理解。
- 靈活性,Shiro 可運(yùn)行在 Web、EJB、IoC、Google App Engine 等任何應(yīng)用環(huán)境,卻不依賴(lài)這些環(huán)境。而 Spring Security 只能與 Spring 一起集成使用。
- 可插拔,Shiro 干凈的 API 和設(shè)計(jì)模式使它可以方便地與許多的其它框架和應(yīng)用進(jìn)行集成。Shiro 可以與諸如 Spring、Grails、Wicket、Tapestry、Mule、Apache Camel、Vaadin 這類(lèi)第三方框架無(wú)縫集成。Spring Security 在這方面就顯得有些捉襟見(jiàn)肘。
spring boot整合shiro
添加maven依賴(lài) 在項(xiàng)目中引入shiro非常簡(jiǎn)單,我們只需要引入 shiro-spring 就可以了
<dependency>
??<groupId>org.apache.shirogroupId>
??<artifactId>shiro-springartifactId>
??<version>1.4.0version>
dependency>
shiro自定義認(rèn)證token
AuthenticationToken 用于收集用戶(hù)提交的身份(如用戶(hù)名)及憑據(jù)(如密碼)。Shiro會(huì)調(diào)用CredentialsMatcher對(duì)象的doCredentialsMatch方法對(duì)AuthenticationInfo對(duì)象和AuthenticationToken進(jìn)行匹配。匹配成功則表示主體(Subject)認(rèn)證成功,否則表示認(rèn)證失敗。
Shiro 僅提供了一個(gè)可以直接使用的 UsernamePasswordToken,用于實(shí)現(xiàn)基于用戶(hù)名/密碼主體(Subject)身份認(rèn)證。UsernamePasswordToken實(shí)現(xiàn)了 RememberMeAuthenticationToken 和 HostAuthenticationToken,可以實(shí)現(xiàn)“記住我”及“主機(jī)驗(yàn)證”的支持。
我們的業(yè)務(wù)邏輯是每次調(diào)用接口,不使用session存儲(chǔ)登錄狀態(tài),使用在head里面存token的方式,所以不使用session,并不需要用戶(hù)密碼認(rèn)證。
自定義token如下:
/**?*?Created?by?Youdmeng?on?2020/6/24?0024.
?*/
public?class?YtoooToken?implements?AuthenticationToken?{
????private?String?token;
????public?YtoooToken(String?token)?{
????????this.token?=?token;
????}
????@Override
????public?Object?getPrincipal()?{
????????return?token;
????}
????@Override
????public?Object?getCredentials()?{
????????return?token;
????}
}
shiro自定義Realm
Realm是shiro的核心組件,主要處理兩大功能:
- 認(rèn)證 我們接收f(shuō)ilter傳過(guò)來(lái)的token,并認(rèn)證login操作的token
- 授權(quán) 獲取到登錄用戶(hù)信息,并取得用戶(hù)的權(quán)限存入roles,以便后期對(duì)接口進(jìn)行操作權(quán)限驗(yàn)證
public?class?UserRealm?extends?AuthorizingRealm?{
????@Autowired
????private?JedisClusterClient?jedis;
????/**
?????*?大坑!,必須重寫(xiě)此方法,不然Shiro會(huì)報(bào)錯(cuò)
?????*/
????@Override
????public?boolean?supports(AuthenticationToken?token)?{
????????return?token?instanceof?YtoooToken;
????}
?????/**
?????*?授權(quán)
?????*
?????*?@param?principals
?????*?@return
?????*/
????@Override
????protected?AuthorizationInfo?doGetAuthorizationInfo(PrincipalCollection?principals)?{
????????log.info("Shiro權(quán)限配置");
????????String?token?=?principals.toString();
????????UserDetailVO?userDetailVO?=?JSON.parseObject(jedis.get(token),?UserDetailVO.class);
????????Set?roles?=?new?HashSet<>();
????????roles.add(userDetailVO.getAuthType()?+?"");
????????SimpleAuthorizationInfo?info?=?new?SimpleAuthorizationInfo();
????????info.setRoles(roles);return?info;
????}/**
?????*?認(rèn)證
?????*
?????*?@param?token
?????*?@return
?????*?@throws?AuthenticationException
?????*/@Overrideprotected?AuthenticationInfo?doGetAuthenticationInfo(AuthenticationToken?token)?throws?AuthenticationException?{
????????log.info("Shiror認(rèn)證");
????????YtoooToken?usToken?=?(YtoooToken)?token;//獲取用戶(hù)的輸入的賬號(hào).
????????String?sid?=?(String)?usToken.getCredentials();if?(StringUtils.isBlank(sid))?{return?null;
????????}
????????log.info("sid:?"?+?sid);return?new?SimpleAccount(sid,?sid,?"userRealm");
????}
}
shiro自定義攔截器
自定義shiro攔截器來(lái)控制指定請(qǐng)求的訪問(wèn)權(quán)限,并登錄shiro以便認(rèn)證
我們自定義shiro攔截器主要使用其中的兩個(gè)方法:
- isAccessAllowed() 判斷是否可以登錄到系統(tǒng)
- onAccessDenied() 當(dāng)isAccessAllowed()返回false時(shí),登錄被拒絕,進(jìn)入此接口進(jìn)行異常處理
public?class?TokenFilter?extends?FormAuthenticationFilter?{
????private?String?errorCode;
????private?String?errorMsg;
????private?static?JedisClusterClient?jedis?=?JedisClusterClient.getInstance();
????/**
?????*?如果在這里返回了false,請(qǐng)求onAccessDenied()
?????*/
????@Override
????protected?boolean?isAccessAllowed(ServletRequest?request,?ServletResponse?response,?Object?mappedValue)?{
????????HttpServletRequest?httpServletRequest?=?(HttpServletRequest)?request;
????????String?sid?=?httpServletRequest.getHeader("sid");
????????if?(StringUtils.isBlank(sid))?{
????????????this.errorCode?=?ResponseEnum.TOKEN_UNAVAILABLE.getCode();
????????????this.errorMsg?=?ResponseEnum.TOKEN_UNAVAILABLE.getMessage();
????????????return?false;
????????}
????????log.info("sid:?"?+?sid);
????????UserDetailVO?userInfo?=?null;
????????try?{
????????????userInfo?=?JSON.parseObject(jedis.get(sid),?UserDetailVO.class);
????????}?catch?(Exception?e)?{
????????????this.errorCode?=?ResponseEnum.TOKEN_EXPIRE.getCode();
????????????this.errorMsg?=?ResponseEnum.TOKEN_EXPIRE.getMessage();
????????????return?false;
????????}
????????if?(userInfo?==?null)?{
????????????this.errorCode?=?ResponseEnum.TOKEN_EXPIRE.getCode();
????????????this.errorMsg?=?ResponseEnum.TOKEN_EXPIRE.getMessage();
????????????return?false;
????????}
????????//刷新超時(shí)時(shí)間
????????jedis.expire(sid,?30?*?60);?//30分鐘過(guò)期
????????YtoooToken?token?=?new?YtoooToken(sid);
????????//?提交給realm進(jìn)行登入,如果錯(cuò)誤他會(huì)拋出異常并被捕獲
????????getSubject(request,?response).login(token);
????????//?如果沒(méi)有拋出異常則代表登入成功,返回true
????????return?true;
????}
????@Override
????protected?boolean?onAccessDenied(ServletRequest?request,?ServletResponse?response)?{
????????ResponseMessage?result?=?Result.error(this.errorCode,this.errorMsg);
????????String?reponseJson?=?(new?Gson()).toJson(result);
????????response.setContentType("application/json;?charset=utf-8");
????????response.setCharacterEncoding("utf-8");
????????ServletOutputStream?outputStream?=?null;
????????try?{
????????????outputStream?=?response.getOutputStream();
????????????outputStream.write(reponseJson.getBytes());
????????}?catch?(IOException?e)?{
????????????log.error("權(quán)限校驗(yàn)異常",e);
????????}?finally?{
????????????if?(outputStream?!=?null){
????????????????try?{
????????????????????outputStream.flush();
????????????????????outputStream.close();
????????????????}?catch?(IOException?e)?{
????????????????????log.error("權(quán)限校驗(yàn),關(guān)閉連接異常",e);
????????????????}
????????????}
????????}
????????return?false;
????}
}
配置ShiroConfig
springboot中,組件通過(guò)@Bean的方式交由spring統(tǒng)一管理,在這里需要配置 securityManager,shiroFilter,AuthorizationAttributeSourceAdvisor
注入realm
@Beanpublic?UserRealm?userRealm()?{
????UserRealm?userRealm?=?new?UserRealm();
????return?userRealm;
}
注入 securityManager
@Bean("securityManager")public?DefaultWebSecurityManager?getManager(UserRealm?realm)?{
????DefaultWebSecurityManager?manager?=?new?DefaultWebSecurityManager();
????//?使用自己的realm
????manager.setRealm(realm);
????/*
??????*?關(guān)閉shiro自帶的session,詳情見(jiàn)文檔
??????*?http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
??????*/
????DefaultSubjectDAO?subjectDAO?=?new?DefaultSubjectDAO();
????DefaultSessionStorageEvaluator?defaultSessionStorageEvaluator?=?new?DefaultSessionStorageEvaluator();
????defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
????subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
????manager.setSubjectDAO(subjectDAO);
????return?manager;
}
注入 shiroFilter
此處將自定義過(guò)濾器添加到shiro中,并配置具體哪些路徑,執(zhí)行shiro的那些過(guò)濾規(guī)則
@Bean("shiroFilter")public?ShiroFilterFactoryBean?factory(DefaultWebSecurityManager?securityManager)?{
????ShiroFilterFactoryBean?factoryBean?=?new?ShiroFilterFactoryBean();
????//?添加自己的過(guò)濾器并且取名為token
????Map?filterMap?=?new?HashMap<>();
????filterMap.put("token",?new?TokenFilter());
????factoryBean.setFilters(filterMap);
????factoryBean.setSecurityManager(securityManager);/*
??????*?自定義url規(guī)則
??????*?http://shiro.apache.org/web.html#urls-
??????*/
????Map?filterRuleMap?=?new?HashMap<>();//swagger
????filterRuleMap.put("/swagger-ui.html",?"anon");
????filterRuleMap.put("/**/*.js",?"anon");
????filterRuleMap.put("/**/*.png",?"anon");
????filterRuleMap.put("/**/*.ico",?"anon");
????filterRuleMap.put("/**/*.css",?"anon");
????filterRuleMap.put("/**/ui/**",?"anon");
????filterRuleMap.put("/**/swagger-resources/**",?"anon");
????filterRuleMap.put("/**/api-docs/**",?"anon");//swagger//登錄
????filterRuleMap.put("/login/login",?"anon");
????filterRuleMap.put("/login/verifyCode",?"anon");//?所有請(qǐng)求通過(guò)我們自己的JWT?Filter
????filterRuleMap.put("/**",?"token");
????factoryBean.setFilterChainDefinitionMap(filterRuleMap);return?factoryBean;
}
配置DefaultAdvisorAutoProxyCreator
解決 在@Controller注解的類(lèi)的方法中加入@RequiresRole等shiro注解,會(huì)導(dǎo)致該方法無(wú)法映射請(qǐng)求,導(dǎo)致返回404。
@Beanpublic?static?DefaultAdvisorAutoProxyCreator?getDefaultAdvisorAutoProxyCreator(){
????DefaultAdvisorAutoProxyCreator?defaultAdvisorAutoProxyCreator=new?DefaultAdvisorAutoProxyCreator();
????/**
??????* setUsePrefix(false)用于解決一個(gè)奇怪的bug。在引入spring aop的情況下。
??????*?在@Controller注解的類(lèi)的方法中加入@RequiresRole等shiro注解,會(huì)導(dǎo)致該方法無(wú)法映射請(qǐng)求,導(dǎo)致返回404。
??????*?加入這項(xiàng)配置能解決這個(gè)bug
??????*/
????defaultAdvisorAutoProxyCreator.setUsePrefix(true);
????return?defaultAdvisorAutoProxyCreator;
}
配置 AuthorizationAttributeSourceAdvisor 使doGetAuthorizationInfo()Shiro權(quán)限配置生效
@Beanpublic?AuthorizationAttributeSourceAdvisor?authorizationAttributeSourceAdvisor(DefaultWebSecurityManager?securityManager)?{
????AuthorizationAttributeSourceAdvisor?authorizationAttributeSourceAdvisor?=?new?AuthorizationAttributeSourceAdvisor();
????authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
????return?authorizationAttributeSourceAdvisor;
}?
在接口中控制權(quán)限
使用RequiresRoles注解來(lái)配置該接口需要的權(quán)限
當(dāng)配置logical = Logical.OR時(shí),登錄配置的權(quán)限在1,2,3中任意一個(gè),既可以成功訪問(wèn)接口
@ApiOperation("任務(wù)調(diào)度")@PostMapping("/dispatch")
@RequiresRoles(value?=?{?"1",?"2",?"3"?},?logical?=?Logical.OR)
public?ResponseMessage?dispatch(@RequestBody?@Valid?DispatchVO?dispatchVO)?{
????log.info("任務(wù)調(diào)度開(kāi)始?入?yún)?"?+?JSON.toJSONString(dispatchVO));
????try?{
????????service.dispatch(dispatchVO);
????????return?Result.success(ResponseEnum.SUCCESS.getCode(),?ResponseEnum.SUCCESS.getMessage());
????}?catch?(RuntimeException?e)?{
????????log.error("任務(wù)調(diào)度失敗",?e);
????????return?Result.error(ResponseEnum.ERROR.getCode(),?e.getMessage());
????}?catch?(Exception?e)?{
????????log.error("任務(wù)調(diào)度失敗",?e);
????????return?Result.error(ResponseEnum.ERROR.getCode(),?ResponseEnum.ERROR.getMessage());
????}
}
統(tǒng)一的異常處理
配置全局異常處理
@ControllerAdvice@Order(value=1)
public?class?ShiroExceptionAdvice?{
????private?static?final?Logger?logger?=?LoggerFactory.getLogger(ShiroExceptionAdvice.class);
????@ResponseStatus(HttpStatus.UNAUTHORIZED)
????@ExceptionHandler({AuthenticationException.class,?UnknownAccountException.class,
????????????UnauthenticatedException.class,?IncorrectCredentialsException.class})
????@ResponseBody
????public?ResponseMessage?unauthorized(Exception?exception)?{
????????logger.warn(exception.getMessage(),?exception);
????????logger.info("catch?UnknownAccountException");
????????return?Result.error(ResponseEnum.NOT_AUTHORIZED.getCode(),?ResponseEnum.NOT_AUTHORIZED.getMessage());
????}
????@ResponseStatus(HttpStatus.UNAUTHORIZED)
????@ExceptionHandler(UnauthorizedException.class)
????@ResponseBody
????public?ResponseMessage?unauthorized1(UnauthorizedException?exception)?{
????????logger.warn(exception.getMessage(),?exception);
????????return?Result.error(ResponseEnum.NOT_AUTHORIZED.getCode(),?ResponseEnum.NOT_AUTHORIZED.getMessage());
????}
}
配置redis
@Bean????@DependsOn("ConfigUtil")
????public?JedisClusterClient?getClient()?{
????????ml.ytooo.redis.RedisProperties.expireSeconds?=?redisProperties.getExpireSeconds();
????????ml.ytooo.redis.RedisProperties.clusterNodes?=?redisProperties.getClusterNodes();
????????ml.ytooo.redis.RedisProperties.connectionTimeout?=?redisProperties.getConnectionTimeout();
????????ml.ytooo.redis.RedisProperties.soTimeout?=?redisProperties.getSoTimeout();
????????ml.ytooo.redis.RedisProperties.maxAttempts?=?redisProperties.getMaxAttempts();
????????if?(StringUtils.isNotBlank(redisProperties.password))?{
????????????ml.ytooo.redis.RedisProperties.password?=?redisProperties.password;
????????}else?{
????????????ml.ytooo.redis.RedisProperties.password?=?null;
????????}
????????return?JedisClusterClient.getInstance();
????}
@Data
@Component
@ConfigurationProperties(prefix?=?"redis.cache")
public?class?RedisProperties?{
????private?int?expireSeconds;
????private?String?clusterNodes;
????private?int??connectionTimeout;
????private?String?password;
????private?int?soTimeout;
????private?int?maxAttempts;
}
依賴(lài)工具集:
<dependency>??<groupId>ml.ytooogroupId>
??<artifactId>ytooo-utilartifactId>
??<version>3.7.0version>
dependency>
關(guān)注我!Java從此不迷路!
作者:我是大月餅
鏈接:
https://blog.csdn.net/Youdmeng/article/details/107179579
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的json web token没有哪个成分_【分享项目】给你看看我们公司的登录认证是怎么做的?!(SpringBoot+Shiro+Token+Redis)...的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: pythonfor循环100次_【零基础
- 下一篇: gradle idea java ssm