總結:上期我們說到表設計,這次來實現。
前后端分離思路:登入返回 token 根據token 獲取菜單數據,動態渲染 路由。重新shiro 的處理邏輯就行,如沒有權限,未認證 登 返貨json 格式。(前端控制 路由跳轉,后端負責數據)
不分離 就常規套路,登入后 獲取菜單信息 渲染頁面,未認證直接跳轉 登入頁,或者沒有權限頁面,也可以通過標簽 直接看不到,反正就是簡單的很。(后端負責所有頁面跳轉和數據扭轉)。
核心代碼如下:
public class CustomRealm extends AuthorizingRealm { @Autowired private SysUsersService sysUsersService; @Autowired private SysUsersMapper usersMapper; /** * 授權 * @param principals * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { if (principals == null) { throw new AuthorizationException("請重新登入"); } // principals 就是 認證 傳的 第一個 參數 傳什么 強轉什么 SysUsers user = (SysUsers) getAvailablePrincipal(principals); List<SysPermissions> list = usersMapper.getPermissions(user.getId()); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); Set<String> permissions = list.stream().map(e->e.getPermission()).collect(Collectors.toSet()); info.setStringPermissions(permissions); return info; } /** * 認證 * @param token * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { UsernamePasswordToken fromToken = (UsernamePasswordToken) token; String username = fromToken.getUsername(); QueryWrapper<SysUsers> queryWrapper = new QueryWrapper(); List<SysUsers> usersList = sysUsersService.list(queryWrapper); if(CollectionUtils.isEmpty(usersList)){ throw new UnknownAccountException("用戶不存在"); } SysUsers user = usersList.get(0); // 一般重次 登入 3次 密碼錯誤 就鎖定 需要重下 shiro 登入失敗 邏輯 if(user.getLocked()>0){ throw new LockedAccountException("賬戶被鎖定,請聯系管理員"); } //從數據 查詢 根據用戶username 用戶 然后 交給 shiro 去匹配 密碼 用戶名 相等 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), getName()); /* if (user.getSalt() != null) { info.setCredentialsSalt(ByteSource.Util.bytes(user.getSalt())); }*/ return info; } }
核心配置如下:
@Configuration public class ShiroConfig { /** * 注入自定義的realm,告訴shiro如何獲取用戶信息來做登錄或權限控制 */ @Bean public Realm realm() { return new CustomRealm(); } /** * 這里統一做鑒權,即判斷哪些請求路徑需要用戶登錄,哪些請求路徑不需要用戶登錄。 * 這里只做鑒權,不做權限控制,因為權限用注解來做。 * @return */ @Bean public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); factoryBean.setSecurityManager(securityManager); Map<String, String> chainDefinition = new LinkedHashMap<>(); chainDefinition.put("/user/index", "anon"); chainDefinition.put("/user/login", "anon"); chainDefinition.put("/static/**", "anon"); chainDefinition.put("/css/**", "anon"); chainDefinition.put("/js/**", "anon"); chainDefinition.put("/swagger-ui.html", "anon"); chainDefinition.put("/doc.html", "anon"); chainDefinition.put("/swagger-resources", "anon"); chainDefinition.put("/swagger-resources/configuration/security", "anon"); chainDefinition.put("/swagger-resources/configuration/ui", "anon"); chainDefinition.put("/v2/api-docs", "anon"); chainDefinition.put("/webjars/springfox-swagger-ui/**", "anon"); // 1、創建過濾器Map,用來裝自定義過濾器 LinkedHashMap<String, Filter> map = new LinkedHashMap<>(); // 2、將自定義過濾器放入map中,如果實現了自定義授權過濾器,那就必須在這里注冊,否則Shiro不會使用自定義的授權過濾器 map.put("authc", new MyFormAuthenticationFilter()); // 3、將過濾器Ma綁定到shiroFilterFactoryBean上 factoryBean.setFilters(map); //設置 未認證跳轉 當然重寫 過濾器 也可以 factoryBean.setLoginUrl("/user/unauth"); //設置 未授權跳轉 factoryBean.setUnauthorizedUrl("/user/unperms"); //除了以上的請求外,其它請求都需要登錄 chainDefinition.put("/**", "authc"); // chainDefinition.put("/**", "anon"); factoryBean.setFilterChainDefinitionMap(chainDefinition); return factoryBean; } @Bean public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator(); /** * setUsePrefix(false)用於解決一個奇怪的bug。在引入spring aop的情況下。 * 在@Controller注解的類的方法中加入@RequiresRole注解,會導致該方法無法映射請求,導致返回404。 * 加入這項配置能解決這個bug */ creator.setProxyTargetClass(true); creator.setUsePrefix(true); return creator; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //securityManager.setRealms(Arrays.asList(realm())); securityManager.setRealm(realm()); // securityManager.setCacheManager(cacheManager()); // 設置realm. 可以設置 多ream 和 策略 //securityManager.setAuthenticator(authenticator()); return securityManager; } /* //添加realm @Bean public Authenticator authenticator() { ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator(); //設置兩個Realm,一個用於用戶登錄驗證和訪問權限獲取;一個用於jwt token的認證 authenticator.setRealms(Arrays.asList(jwtRealm())); //設置多個realm認證策略,一個成功即跳過其它的 authenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy()); return authenticator; }*/ /** * 使用默認session * * @return */ @Bean(name="sessionManager") public ServletContainerSessionManager servletContainerSessionManager() { ServletContainerSessionManager sessionManager = new ServletContainerSessionManager(); return sessionManager; } /* *//** * 憑證匹配器 * @return *//* @Bean(name = "hashedCredentialsMatcher") public HashedCredentialsMatcher getHashedCredentialsMatcher(){ HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher("SHA-256"); hashedCredentialsMatcher.setHashIterations(2); hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true); return hashedCredentialsMatcher; }*/ }
效果如下:
接口文檔如下:
就是折磨簡單 雖然沒啥難度,但是今天還是搞了半天。
代碼結構:
源碼地址 有興趣的可以看看
https://github.com/lyc88/shiro