国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 開(kāi)發(fā) > Java > 正文

spring boot 集成 shiro 自定義密碼驗(yàn)證 自定義freemarker標(biāo)簽根據(jù)權(quán)限渲染不同頁(yè)面

2024-07-14 08:43:01
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

項(xiàng)目里一直用的是 spring-security ,不得不說(shuō),spring-security 真是東西太多了,學(xué)習(xí)難度太大(可能我比較菜),這篇博客來(lái)總結(jié)一下折騰shiro的成果,分享給大家,強(qiáng)烈推薦shiro,真心簡(jiǎn)單 : )

引入依賴

<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version></dependency>

用戶,角色,權(quán)限

就是經(jīng)典的RBAC權(quán)限系統(tǒng),下面簡(jiǎn)單給一下實(shí)體類(lèi)字段

AdminUser.java

public class AdminUser implements Serializable { private static final long serialVersionUID = 8264158018518861440L; private Integer id; private String username; private String password; private Integer roleId; // getter setter...}

Role.java

public class Role implements Serializable { private static final long serialVersionUID = 7824693669858106664L; private Integer id; private String name; // getter setter...}

Permission.java

public class Permission implements Serializable { private static final long serialVersionUID = -2694960432845360318L; private Integer id; private String name; private String value; // 權(quán)限的父節(jié)點(diǎn)的id private Integer pid; // getter setter...}

自定義Realm

這貨就是查詢用戶的信息然后放在shiro的個(gè)人用戶對(duì)象的緩存里,shiro自己有一個(gè)session的對(duì)象(不是servlet里的session)作用就是后面用戶發(fā)起請(qǐng)求的時(shí)候拿來(lái)判斷有沒(méi)有權(quán)限

另一個(gè)作用是查詢一下用戶的信息,將用戶名,密碼組裝成一個(gè) AuthenticationInfo 用于后面密碼校驗(yàn)的

具體代碼如下

MyShiroRealm.java

 

@Componentpublic class MyShiroRealm extends AuthorizingRealm { private Logger log = LoggerFactory.getLogger(MyShiroRealm.class); @Autowired private AdminUserService adminUserService; @Autowired private RoleService roleService; @Autowired private PermissionService permissionService; // 用戶權(quán)限配置 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { //訪問(wèn)@RequirePermission注解的url時(shí)觸發(fā) SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); AdminUser adminUser = adminUserService.selectByUsername(principals.toString()); //獲得用戶的角色,及權(quán)限進(jìn)行綁定 Role role = roleService.selectById(adminUser.getRoleId()); // 其實(shí)這里也可以不要權(quán)限那個(gè)類(lèi)了,直接用角色這個(gè)類(lèi)來(lái)做鑒權(quán), // 不過(guò)角色包含很多的權(quán)限,已經(jīng)算是大家約定的了,所以下面還是查詢權(quán)限然后放在AuthorizationInfo里 simpleAuthorizationInfo.addRole(role.getName()); // 查詢權(quán)限 List<Permission> permissions = permissionService.selectByRoleId(adminUser.getRoleId()); // 將權(quán)限具體值取出來(lái)組裝成一個(gè)權(quán)限String的集合 List<String> permissionValues = permissions.stream().map(Permission::getValue).collect(Collectors.toList()); // 將權(quán)限的String集合添加進(jìn)AuthorizationInfo里,后面請(qǐng)求鑒權(quán)有用 simpleAuthorizationInfo.addStringPermissions(permissionValues); return simpleAuthorizationInfo; } // 組裝用戶信息 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String username = (String) token.getPrincipal(); log.info("用戶:{} 正在登錄...", username); AdminUser adminUser = adminUserService.selectByUsername(username); // 如果用戶不存在,則拋出未知用戶的異常 if (adminUser == null) throw new UnknownAccountException(); return new SimpleAuthenticationInfo(username, adminUser.getPassword(), getName()); }}

實(shí)現(xiàn)密碼校驗(yàn)

shiro內(nèi)置了幾個(gè)密碼校驗(yàn)的類(lèi),有 Md5CredentialsMatcher Sha1CredentialsMatcher , 不過(guò)從1.1版本開(kāi)始,都開(kāi)始使用 HashedCredentialsMatcher 這個(gè)類(lèi)了,通過(guò)配置加密規(guī)則來(lái)校驗(yàn)

它們都實(shí)現(xiàn)了一個(gè)接口 CredentialsMatcher 我這里也實(shí)現(xiàn)這個(gè)接口,實(shí)現(xiàn)一個(gè)自己的密碼校驗(yàn)

說(shuō)明一下,我這里用的加密方式是Spring-Security里的 BCryptPasswordEncoder 作的加密,之所以用它,是因?yàn)橥粋€(gè)密碼被這貨加密后,密文都不一樣,下面是具體代碼

public class MyCredentialsMatcher implements CredentialsMatcher { @Override public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { // 大坑!?。。。。。。。。。。。。。。。。?! // 明明token跟info兩個(gè)對(duì)象的里的Credentials類(lèi)型都是Object,斷點(diǎn)看到的類(lèi)型都是 char[] // 但是?。。。?! token里轉(zhuǎn)成String要先強(qiáng)轉(zhuǎn)成 char[] // 而info里取Credentials就可以直接使用 String.valueOf() 轉(zhuǎn)成String // 醉了。。 String rawPassword = String.valueOf((char[]) token.getCredentials()); String encodedPassword = String.valueOf(info.getCredentials()); return new BCryptPasswordEncoder().matches(rawPassword, encodedPassword); }}

配置shiro

因?yàn)轫?xiàng)目是spring-boot開(kāi)發(fā)的,shiro就用java代碼配置,不用xml配置, 具體配置如下

 

@Configurationpublic class ShiroConfig { private Logger log = LoggerFactory.getLogger(ShiroConfig.class); @Autowired private MyShiroRealm myShiroRealm; @Bean public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { log.info("開(kāi)始配置shiroFilter..."); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); //攔截器. Map<String,String> map = new HashMap<>(); // 配置不會(huì)被攔截的鏈接 順序判斷 相關(guān)靜態(tài)資源 map.put("/static/**", "anon"); //配置退出 過(guò)濾器,其中的具體的退出代碼Shiro已經(jīng)替我們實(shí)現(xiàn)了 map.put("/admin/logout", "logout"); //<!-- 過(guò)濾鏈定義,從上向下順序執(zhí)行,一般將/**放在最為下邊 -->:這是一個(gè)坑呢,一不小心代碼就不好使了; //<!-- authc:所有url都必須認(rèn)證通過(guò)才可以訪問(wèn); anon:所有url都都可以匿名訪問(wèn)--> map.put("/admin/**", "authc"); // 如果不設(shè)置默認(rèn)會(huì)自動(dòng)尋找Web工程根目錄下的"/login.jsp"頁(yè)面 shiroFilterFactoryBean.setLoginUrl("/adminlogin"); // 登錄成功后要跳轉(zhuǎn)的鏈接 shiroFilterFactoryBean.setSuccessUrl("/admin/index"); //未授權(quán)界面; shiroFilterFactoryBean.setUnauthorizedUrl("/error"); shiroFilterFactoryBean.setFilterChainDefinitionMap(map); return shiroFilterFactoryBean; } // 配置加密方式 // 配置了一下,這貨就是驗(yàn)證不過(guò),,改成手動(dòng)驗(yàn)證算了,以后換加密方式也方便 @Bean public MyCredentialsMatcher myCredentialsMatcher() { return new MyCredentialsMatcher(); } // 安全管理器配置 @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); myShiroRealm.setCredentialsMatcher(myCredentialsMatcher()); securityManager.setRealm(myShiroRealm); return securityManager; }}

登錄

都配置好了,就可以發(fā)起登錄請(qǐng)求做測(cè)試了,一個(gè)簡(jiǎn)單的表單即可,寫(xiě)在Controller里就行

 

@PostMapping("/adminlogin")public String adminLogin(String username, String password,       @RequestParam(defaultValue = "0") Boolean rememberMe,       RedirectAttributes redirectAttributes) { try { // 添加用戶認(rèn)證信息 Subject subject = SecurityUtils.getSubject(); if (!subject.isAuthenticated()) {  UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe);  //進(jìn)行驗(yàn)證,這里可以捕獲異常,然后返回對(duì)應(yīng)信息  subject.login(token); } } catch (AuthenticationException e) { // e.printStackTrace(); log.error(e.getMessage()); redirectAttributes.addFlashAttribute("error", "用戶名或密碼錯(cuò)誤"); redirectAttributes.addFlashAttribute("username", username); return redirect("/adminlogin"); } return redirect("/admin/index");}

從上面代碼可以看出,記住我功能也直接都實(shí)現(xiàn)好了,只需要在組裝 UsernamePasswordToken 的時(shí)候,將記住我字段傳進(jìn)去就可以了,值是 true, false, 如果是true,登錄成功后,shiro會(huì)在本地寫(xiě)一個(gè)cookie

調(diào)用 subject.login(token); 方法后,它會(huì)去鑒權(quán),期間會(huì)產(chǎn)生各種各樣的異常,有以下幾種,可以通過(guò)捕捉不同的異常然后提示頁(yè)面不同的錯(cuò)誤信息,相當(dāng)?shù)姆奖阊剑心居?/p>

  • AccountException 帳戶異常
  • ConcurrentAccessException 這個(gè)好像是并發(fā)異常
  • CredentialsException 密碼校驗(yàn)異常
  • DisabledAccountException 帳戶被禁異常
  • ExcessiveAttemptsException 嘗試登錄次數(shù)過(guò)多異常
  • ExpiredCredentialsException 認(rèn)證信息過(guò)期異常
  • IncorrectCredentialsException 密碼不正確異常
  • LockedAccountException 帳戶被鎖定異常
  • UnknownAccountException 未知帳戶異常
  • UnsupportedTokenException login(AuthenticationToken) 這個(gè)方法只能接收 AuthenticationToken 類(lèi)的對(duì)象,如果傳的是其它的類(lèi),就拋這個(gè)異常

上面這么多異常,shiro在處理登錄的邏輯時(shí),會(huì)自動(dòng)的發(fā)出一些異常,當(dāng)然你也可以手動(dòng)去處理登錄流程,然后根據(jù)不同的問(wèn)題拋出不同的異常,手動(dòng)處理的地方就在自己寫(xiě)的 MyShiroRealm 里的 doGetAuthenticationInfo() 方法里,我在上面代碼里只處理了一個(gè)帳戶不存在時(shí)拋出了一個(gè) UnknownAccountException 的異常,其實(shí)還可以加更多其它的異常,這個(gè)要看個(gè)人系統(tǒng)的需求來(lái)定了

到這里已經(jīng)可以正常的實(shí)現(xiàn)登錄了,下面來(lái)說(shuō)一些其它相關(guān)的功能的實(shí)現(xiàn)

自定freemarker標(biāo)簽

開(kāi)發(fā)項(xiàng)目肯定要用到頁(yè)面模板,我這里用的是 freemarker ,一個(gè)用戶登錄后,頁(yè)面可能要根據(jù)用戶的不同權(quán)限渲染不同的菜單,github上有個(gè)開(kāi)源的庫(kù),也是可以用的,不過(guò)我覺(jué)得那個(gè)太麻煩了,就自己實(shí)現(xiàn)了一個(gè),幾行代碼就能搞定

ShiroTag.java

 

@Componentpublic class ShiroTag { // 判斷當(dāng)前用戶是否已經(jīng)登錄認(rèn)證過(guò) public boolean isAuthenticated(){ return SecurityUtils.getSubject().isAuthenticated(); } // 獲取當(dāng)前用戶的用戶名 public String getPrincipal() { return (String) SecurityUtils.getSubject().getPrincipal(); } // 判斷用戶是否有 xx 角色 public boolean hasRole(String name) { return SecurityUtils.getSubject().hasRole(name); } // 判斷用戶是否有 xx 權(quán)限 public boolean hasPermission(String name) { return !StringUtils.isEmpty(name) && SecurityUtils.getSubject().isPermitted(name); }}

將這個(gè)類(lèi)注冊(cè)到freemarker的全局變量里

FreemarkerConfig.java

@Configurationpublic class FreemarkerConfig { private Logger log = LoggerFactory.getLogger(FreeMarkerConfig.class); @Autowired private ShiroTag shiroTag; @PostConstruct public void setSharedVariable() throws TemplateModelException { //注入全局配置到freemarker log.info("開(kāi)始配置freemarker全局變量..."); // shiro鑒權(quán) configuration.setSharedVariable("sec", shiroTag); log.info("freemarker自定義標(biāo)簽配置完成!"); }}

有了這些配置后,就可以在頁(yè)面里使用了,具體用法如下

<#if sec.hasPermission("topic:list")> <li <#if page_tab=='topic'>class="active"</#if>> <a href="/admin/topic/list" rel="external nofollow" >  <i class="fa fa-list"></i>  <span>話題列表</span> </a> </li></#if>

加上這個(gè)后,在渲染頁(yè)面的時(shí)候,就會(huì)根據(jù)當(dāng)前用戶是否有查看話題列表的權(quán)限,然后來(lái)渲染這個(gè)菜單

注解權(quán)限

有了上面freemarker標(biāo)簽判斷是否有權(quán)限來(lái)渲染頁(yè)面,這樣做只能防君子,不能防小人,如果一個(gè)人知道后臺(tái)的某個(gè)訪問(wèn)鏈接,但這個(gè)鏈接它是沒(méi)有權(quán)限訪問(wèn)的,那他只要手動(dòng)輸入這個(gè)鏈接就還是可以訪問(wèn)的,所以這里還要在Controller層加一套防御,具體配置如下

在ShiroConfig里加上兩個(gè)Bean

//加入注解的使用,不加入這個(gè)注解不生效@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor;}@Bean@ConditionalOnMissingBeanpublic DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator(); defaultAAP.setProxyTargetClass(true); return defaultAAP;}

有了這兩個(gè)Bean就可以用shiro的注解鑒權(quán)了,用法如下 @RequiresPermissions("topic:list")

 

@Controller@RequestMapping("/admin/topic")public class TopicAdminController extends BaseAdminController { @RequiresPermissions("topic:list") @GetMapping("/list") public String list() { // TODO return "admin/topic/list"; }}

shiro除了 @RequiresPermissions 注解外,還有其它幾個(gè)鑒權(quán)的注解

  • @RequiresPermissions
  • @RequiresRoles
  • @RequiresUser
  • @RequiresGuest
  • @RequiresAuthentication

一般 @RequiresPermissions 就夠用了

總結(jié)

spring-boot 集成 shiro 到這就結(jié)束了,是不是網(wǎng)上能找到的教程里最全的!相比 spring-security 要簡(jiǎn)單太多了,強(qiáng)烈推薦


注:相關(guān)教程知識(shí)閱讀請(qǐng)移步到JAVA教程頻道。
發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 调兵山市| 郸城县| 凉城县| 会昌县| 垦利县| 于都县| 积石山| 泰顺县| 横山县| 富阳市| 平和县| 罗定市| 长宁县| 贡觉县| 房山区| 金昌市| 铜梁县| 竹山县| 吴江市| 吉木萨尔县| 张掖市| 东方市| 奉贤区| 扶风县| 宁国市| 尼勒克县| 酒泉市| 鄂州市| 茂名市| 永清县| 荣成市| 黄冈市| 桃园县| 前郭尔| 怀仁县| 容城县| 灵宝市| 青铜峡市| 神木县| 伊金霍洛旗| 金寨县|