關於 Apache Shiro 概念基本都粘自官網 http://shiro.apache.org/
詳細中文博客 http://wiki.jikexueyuan.com/p...
與SpringBoot整合 https://segmentfault.com/a/11...
Apache Shiro是一個功能強大且靈活的開源安全框架,能夠清晰地處理身份驗證,受權,企業會話管理和加密。html
如下是Apache Shiro能夠作的一些事情:前端
能夠參考官方文檔:http://shiro.apache.org/archi...web
緩存架包先用 ehcachespring
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-all</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> <version>2.10.6</version> </dependency>
<?xml version="1.0" encoding="UTF-8"?> <ehcache> <!-- mac 電腦, 跟 win 設置路徑有點不同 示例: path="d:/ehcache/" --> <diskStore path="${user.home}/Downloads/ehcache" /> <!-- 默認緩存配置 沒有特別指定就用這個配置 --> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="3600" overflowToDisk="true" maxElementsOnDisk="10000000" diskPersistent="false" memoryStoreEvictionPolicy="LRU" diskExpiryThreadIntervalSeconds="120" /> </ehcache>
<!-- 1. 配置 shiro 攔截器 --> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
<!-- 2. 配置shiro的核心組件 SecurityManager --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <!-- 配置緩存 --> <property name="cacheManager" ref="cacheManager" /> <!-- 自定義的 realm --> <property name="realm"> <bean class="com.ogemray.shiro.MyShiroRealm"> <!-- 自定義 realm 裏面的的密碼匹對器 --> <property name="credentialsMatcher"> <bean class="com.ogemray.shiro.MyCredentialsMatcher"/> </property> </bean> </property> </bean> <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"></property> </bean> <!-- 3. 配置shiroFilter,配置shiro的一些基本規則信息,id必須和web.xml中配置的攔截器名字同樣(DelegatingFilterProxy 經過名字去spring容器中找注入的攔截器) --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/login.html"/> <!-- loginUrl 表示登陸頁面地址 --> <property name="successUrl" value="/admin/main.html"/> <!-- successUrl 表示登陸成功後跳轉的頁面 --> <property name="unauthorizedUrl" value="/unauthorized.html"/> <!-- 配置沒有受權跳轉頁面 --> <property name="filterChainDefinitions"> <value> <!--/logout.action=logout --> <!-- logout 表示登出, 清空session, 這裏不須要了, 由於已經在登出對象的方法裏手動清空了 [ SecurityUtils.getSubject().logout() ] --> /admin/userlist*=roles[user] /admin/adduser*=roles[user,admin] <!-- 表示擁有 user 角色 而且 擁有 admin 角色 --> /admin/editRPRelation*=roles[admin],perms[user:insert,user:update,user:select,user:delete] /admin/editURRelation*=perms[user:select] /admin/**=authc /**=anon </value> </property> </bean>
攔截器 shiroFilter 的基本數據數據庫
關於過濾器能夠參考shiro提供的枚舉類 org.apache.shiro.web.filter.mgt.DefaultFilterapache
url 模式使用 Ant 風格模式
Ant 路徑通配符支持?、、,注意通配符匹配不包括目錄分隔符 「/」:
?:匹配一個字符,如」/admin?」 將匹配 / admin1,但不匹配 / admin 或 / admin2;
:匹配零個或多個字符串,如 / admin * 將匹配 / admin、/admin123,但不匹配 / admin/1;
:匹配路徑中的零個或多個路徑,如 / admin/ 將匹配 / admin/a 或 / admin/a/b。segmentfault
public class MyShiroRealm extends AuthorizingRealm { @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("********************* 進行登陸認證 *********************"); String username = (String) authenticationToken.getPrincipal(); //獲取提交的用戶名 User user = userRepository.findByUsername(username); if (user == null) throw new UnknownAccountException("用戶不存在, 請先註冊而後再來登陸"); if (user.getState() == 1) throw new LockedAccountException("該用戶已經被管理員禁用, 請換個帳號登陸"); //接下來進行密碼的比對邏輯 //參數 principal 做爲下面受權部分參數集合裏面的一部分 //參數 credentials 做爲後面與token裏面密碼比對基礎 //返回值 info 做爲下面自定義密碼匹類裏面比對方法的參數 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), getName()); return info; } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("********************* 進行受權認證 *********************"); User user = (User) principalCollection.asList().get(0); //獲得該用戶的全部角色和權限 Set<String> roles = new HashSet<>(); Set<String> permissions = new HashSet<>(); user.getRoles().forEach(role -> { roles.add(role.getRoleName()); role.getPermissions().forEach(permission -> { permissions.add(permission.getPermissionName()); }); }); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.setRoles(roles); info.setStringPermissions(permissions); return info; } @Autowired private UserRepository userRepository; }
前端密碼加密規則:ciphertext_pwd = AES.encrypt(MD5(password))
後端解密密碼規則:md5_password = AES.desEncrypt(ciphertext_pwd)
後端匹對密碼規則:(md5_password + 用戶名作鹽值) 進行 1024 次 MD5 轉換,而後與數據庫取出密碼作比對後端
public class MyCredentialsMatcher implements CredentialsMatcher { @Override public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { String tokenCredentials = new String((char[])token.getCredentials()); //前端傳過來的密碼 String accountCredentials = (String) info.getCredentials(); //數據庫查詢到的密碼 //首先對前端傳過來的密碼進行AES解密 -> 清空 session 裏面的key Session session = SecurityUtils.getSubject().getSession(); String key = (String) session.getAttribute("AESKey"); try { tokenCredentials = AesEncryptUtil.desEncrypt(tokenCredentials, key, key); } catch (Exception e) { throw new IncorrectCredentialsException("可能受到重放攻擊, AES 解密失敗"); } session.removeAttribute("AESKey"); //加密方式 待加密數據 加密鹽值 加密次數 SimpleHash simpleHash = new SimpleHash("MD5", tokenCredentials, token.getPrincipal(), 1024); tokenCredentials = simpleHash.toString(); return accountCredentials.equals(tokenCredentials); } }
@Service("userService") public class UserServiceImpl implements UserService { @Override public void registerUser(User user) { if (userRepository.existsByUsername(user.getUsername())) { throw new RuntimeException("用戶名已經被註冊, 請換個用戶名"); } user.setState((byte) 0); //密碼進行加密 SimpleHash simpleHash = new SimpleHash("MD5", user.getPassword(), user.getUsername(), 1024); user.setPassword(simpleHash.toString()); userRepository.save(user); } @Override public void login(User user) { Subject currentUser = SecurityUtils.getSubject(); if (currentUser.isAuthenticated() == false) { //沒有登陸過須要進行登陸驗證 UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword(), false); currentUser.login(token); } } @Autowired private UserRepository userRepository; }
從上面咱們能夠看到url地址規則匹配能夠配置多角色和多權限,當對應多個角色和權限時中間用 「,」 隔開。spring-mvc
以對應多角色爲例緩存
/admin/userlist*=roles[user] /admin/adduser*=roles[user,admin]
當訪問第二個 /admin/adduser*
時須要同時擁有 user
和 admin
角色,可是有時咱們須要他們之間是或者的關係,這個時候咱們就須要自定義對應的過濾器。
以自定角色過濾器爲例
<!-- 配置shiroFilter,配置shiro的一些基本規則信息 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/login.html"/> <property name="successUrl" value="/admin/main.html"/> <property name="unauthorizedUrl" value="/unauthorized.html"/> <property name="filterChainDefinitions"> <value> ... /admin/userlist*=roles[user] /admin/adduser*=roles[user,admin] ... </value> </property> <property name="filters"> <map> <entry key="perms"> <bean class="com.ogemray.shiro.MyRolesAuthorizationFilter"></bean> </entry> </map> </property> </bean>
public class MyRolesAuthorizationFilter extends AuthorizationFilter { public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException { Subject subject = this.getSubject(request, response); String[] rolesArray = (String[])((String[])mappedValue); if (rolesArray != null && rolesArray.length != 0) { for (String role : rolesArray) { if (subject.hasRole(role)) return true; } } return false; } }
啓動web項目看下自定義過濾器有沒有加進去
在有些方法多,可是權限分的細的地方用註解要比用配置的方案來的方便
使用註解首先要在spring-mvc.xml配置文件中加入如下配置
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"></bean> <!-- 開啓shiro的註解支持 --> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"> <!-- 必須改成true,即便用cglib方式爲Action建立代理對象。默認值爲false,使用JDK建立代理對象,會形成問題 --> <property name="proxyTargetClass" value="true"/> </bean> <!-- 使用shiro框架提供的切面類,用於建立代理對象 --> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"></property> </bean>
在spring.xml配置文件對於過濾器的配置就簡單多了
沒有那些繁雜的配置規則和跳轉頁面
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> </bean>
5個權限註解
上面全部要求的權限和認證沒有時就會拋出對應的異常,只需在SpringMVC中寫好對應的異常截獲方法便可
示例
//表示須要認證 @RequiresAuthentication //表示在須要認證的基礎上要同時擁有 user 和 admin 角色 @RequiresRoles(value = {"user", "admin"}) //同上 @RequiresRoles(value = {"user", "admin"}, logical = Logical.AND) //表示在須要認證的基礎上擁有 user 或 admin 角色 @RequiresRoles(value = {"user", "admin"}, logical = Logical.OR)