總結:上期我們說到表設計,這次來實現。
前后端分離思路:登入返回 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
