照例又去官網扒了扒介紹:html
Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.
Apache Shiro™是一個強大且易用的Java安全框架,可以用於身份驗證、受權、加密和會話管理。Shiro擁有易於理解的API,您能夠快速、輕鬆地得到任何應用程序——從最小的移動應用程序到最大的網絡和企業應用程序。java
簡而言之,Apache Shiro 是一個強大靈活的開源安全框架,能夠徹底處理身份驗證、受權、加密和會話管理。mysql
Shiro能到底能作些什麼呢?git
使用 Shiro 官方給了許多使人信服的緣由,由於 Shiro 具備如下幾個特色:github
有興趣的能夠去仔細看看官方的文檔:【傳送門】web
Apache Shiro是一個全面的、蘊含豐富功能的安全框架。下圖爲描述Shiro功能的框架圖:算法
Authentication(認證), Authorization(受權), Session Management(會話管理), Cryptography(加密)被 Shiro 框架的開發團隊稱之爲應用安全的四大基石。那麼就讓咱們來看看它們吧:spring
還有其餘的功能來支持和增強這些不一樣應用環境下安全領域的關注點。特別是對如下的功能支持:sql
注意: Shiro不會去維護用戶、維護權限,這些須要咱們本身去設計/提供,而後經過相應的接口注入給Shiro數據庫
在概念層,Shiro 架構包含三個主要的理念:Subject,SecurityManager和 Realm。下面的圖展現了這些組件如何相互做用,咱們將在下面依次對其進行描述。
咱們須要實現Realms的Authentication 和 Authorization。其中 Authentication 是用來驗證用戶身份,Authorization 是受權訪問控制,用於對用戶進行的操做受權,證實該用戶是否容許進行當前操做,如訪問某個連接,某個資源文件等。
上圖展現了 Shiro 認證的一個重要的過程,爲了加深咱們的印象,咱們來本身動手來寫一個例子,來驗證一下,首先咱們新建一個Maven工程,而後在pom.xml中引入相關依賴:
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency>
新建一個【AuthenticationTest】測試類:
import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.mgt.DefaultSecurityManager; import org.apache.shiro.realm.SimpleAccountRealm; import org.apache.shiro.subject.Subject; import org.junit.Before; import org.junit.Test; public class AuthenticationTest { SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm(); @Before // 在方法開始前添加一個用戶 public void addUser() { simpleAccountRealm.addAccount("wmyskxz", "123456"); } @Test public void testAuthentication() { // 1.構建SecurityManager環境 DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(); defaultSecurityManager.setRealm(simpleAccountRealm); // 2.主體提交認證請求 SecurityUtils.setSecurityManager(defaultSecurityManager); // 設置SecurityManager環境 Subject subject = SecurityUtils.getSubject(); // 獲取當前主體 UsernamePasswordToken token = new UsernamePasswordToken("wmyskxz", "123456"); subject.login(token); // 登陸 // subject.isAuthenticated()方法返回一個boolean值,用於判斷用戶是否定證成功 System.out.println("isAuthenticated:" + subject.isAuthenticated()); // 輸出true subject.logout(); // 登出 System.out.println("isAuthenticated:" + subject.isAuthenticated()); // 輸出false } }
運行以後能夠看到預想中的效果,先輸出isAuthenticated:true
表示登陸認證成功,而後再輸出isAuthenticated:false
表示認證失敗退出登陸,再來一張圖加深一下印象:
流程以下:
跟認證過程大體類似,下面咱們仍然經過代碼來熟悉一下過程(引入包相似這裏節約篇幅就不貼出來了):
public class AuthenticationTest { SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm(); @Before // 在方法開始前添加一個用戶,讓它具有admin和user兩個角色 public void addUser() { simpleAccountRealm.addAccount("wmyskxz", "123456", "admin", "user"); } @Test public void testAuthentication() { // 1.構建SecurityManager環境 DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(); defaultSecurityManager.setRealm(simpleAccountRealm); // 2.主體提交認證請求 SecurityUtils.setSecurityManager(defaultSecurityManager); // 設置SecurityManager環境 Subject subject = SecurityUtils.getSubject(); // 獲取當前主體 UsernamePasswordToken token = new UsernamePasswordToken("wmyskxz", "123456"); subject.login(token); // 登陸 // subject.isAuthenticated()方法返回一個boolean值,用於判斷用戶是否定證成功 System.out.println("isAuthenticated:" + subject.isAuthenticated()); // 輸出true // 判斷subject是否具備admin和user兩個角色權限,如沒有則會報錯 subject.checkRoles("admin","user"); // subject.checkRole("xxx"); // 報錯 } }
運行測試,可以正確看到效果。
從上面咱們瞭解到實際進行權限信息驗證的是咱們的 Realm,Shiro 框架內部默認提供了兩種實現,一種是查詢.ini
文件的IniRealm
,另外一種是查詢數據庫的JdbcRealm
,這兩種來講都相對簡單,感興趣的能夠去【這裏】瞄兩眼,咱們着重就來介紹介紹自定義實現的 Realm 吧。
有了上面的對認證和受權的理解,咱們先在合適的包下建立一個【MyRealm】類,繼承 Shirot 框架的 AuthorizingRealm 類,並實現默認的兩個方法:
package com.wmyskxz.demo.realm; import org.apache.shiro.authc.*; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import java.util.*; public class MyRealm extends AuthorizingRealm { /** * 模擬數據庫數據 */ Map<String, String> userMap = new HashMap<>(16); { userMap.put("wmyskxz", "123456"); super.setName("myRealm"); // 設置自定義Realm的名稱,取什麼無所謂.. } /** * 受權 * * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { String userName = (String) principalCollection.getPrimaryPrincipal(); // 從數據庫獲取角色和權限數據 Set<String> roles = getRolesByUserName(userName); Set<String> permissions = getPermissionsByUserName(userName); SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); simpleAuthorizationInfo.setStringPermissions(permissions); simpleAuthorizationInfo.setRoles(roles); return simpleAuthorizationInfo; } /** * 模擬從數據庫中獲取權限數據 * * @param userName * @return */ private Set<String> getPermissionsByUserName(String userName) { Set<String> permissions = new HashSet<>(); permissions.add("user:delete"); permissions.add("user:add"); return permissions; } /** * 模擬從數據庫中獲取角色數據 * * @param userName * @return */ private Set<String> getRolesByUserName(String userName) { Set<String> roles = new HashSet<>(); roles.add("admin"); roles.add("user"); return roles; } /** * 認證 * * @param authenticationToken 主體傳過來的認證信息 * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { // 1.從主體傳過來的認證信息中,得到用戶名 String userName = (String) authenticationToken.getPrincipal(); // 2.經過用戶名到數據庫中獲取憑證 String password = getPasswordByUserName(userName); if (password == null) { return null; } SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo("wmyskxz", password, "myRealm"); return authenticationInfo; } /** * 模擬從數據庫取憑證的過程 * * @param userName * @return */ private String getPasswordByUserName(String userName) { return userMap.get(userName); } }
而後咱們編寫測試類,來驗證是否正確:
import com.wmyskxz.demo.realm.MyRealm; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.mgt.DefaultSecurityManager; import org.apache.shiro.subject.Subject; import org.junit.Test; public class AuthenticationTest { @Test public void testAuthentication() { MyRealm myRealm = new MyRealm(); // 實現本身的 Realm 實例 // 1.構建SecurityManager環境 DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(); defaultSecurityManager.setRealm(myRealm); // 2.主體提交認證請求 SecurityUtils.setSecurityManager(defaultSecurityManager); // 設置SecurityManager環境 Subject subject = SecurityUtils.getSubject(); // 獲取當前主體 UsernamePasswordToken token = new UsernamePasswordToken("wmyskxz", "123456"); subject.login(token); // 登陸 // subject.isAuthenticated()方法返回一個boolean值,用於判斷用戶是否定證成功 System.out.println("isAuthenticated:" + subject.isAuthenticated()); // 輸出true // 判斷subject是否具備admin和user兩個角色權限,如沒有則會報錯 subject.checkRoles("admin", "user"); // subject.checkRole("xxx"); // 報錯 // 判斷subject是否具備user:add權限 subject.checkPermission("user:add"); } }
運行測試,完美。
在以前的學習中,咱們在數據庫中保存的密碼都是明文的,一旦數據庫數據泄露,那就會形成不可估算的損失,因此咱們一般都會使用非對稱加密,簡單理解也就是不可逆的加密,而 md5 加密算法就是符合這樣的一種算法。
如上面的 123456 用 Md5 加密後,獲得的字符串:e10adc3949ba59abbe56e057f20f883e,就沒法經過計算還原回 123456,咱們把這個加密的字符串保存在數據庫中,等下次用戶登陸時咱們把密碼經過一樣的算法加密後再從數據庫中取出這個字符串進行比較,就可以知道密碼是否正確了,這樣既保留了密碼驗證的功能又大大增長了安全性,可是問題是:雖然沒法直接經過計算反推回密碼,可是咱們仍然能夠經過計算一些簡單的密碼加密後的 Md5 值進行比較,推算出原來的密碼
好比個人密碼是 123456,你的密碼也是,經過 md5 加密以後的字符串一致,因此你也就能知道個人密碼了,若是咱們把經常使用的一些密碼都作 md5 加密獲得一本字典,那麼就能夠獲得至關一部分的人密碼,這也就至關於「破解」了同樣,因此其實也沒有咱們想象中的那麼「安全」。
既然相同的密碼 md5 同樣,那麼咱們就讓咱們的原始密碼再加一個隨機數,而後再進行 md5 加密,這個隨機數就是咱們說的鹽(salt),這樣處理下來就能獲得不一樣的 Md5 值,固然咱們須要把這個隨機數鹽也保存進數據庫中,以便咱們進行驗證。
另外咱們能夠經過屢次加密的方法,即便黑客經過必定的技術手段拿到了咱們的密碼 md5 值,但它並不知道咱們到底加密了多少次,因此這也使得破解工做變得艱難。
在 Shiro 框架中,對於這樣的操做提供了簡單的代碼實現:
String password = "123456"; String salt = new SecureRandomNumberGenerator().nextBytes().toString(); int times = 2; // 加密次數:2 String alogrithmName = "md5"; // 加密算法 String encodePassword = new SimpleHash(alogrithmName, password, salt, times).toString(); System.out.printf("原始密碼是 %s , 鹽是: %s, 運算次數是: %d, 運算出來的密文是:%s ",password,salt,times,encodePassword);
輸出:
原始密碼是 123456 , 鹽是: f5GQZsuWjnL9z585JjLrbQ==, 運算次數是: 2, 運算出來的密文是:55fee80f73537cefd6b3c9a920993c25
經過上面的學習,咱們如今來着手搭建一個簡單的使用 Shiro 進行權限驗證受權的一個簡單系統
pom包:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency>
application.properties文件:
#thymeleaf 配置 spring.thymeleaf.mode=HTML5 spring.thymeleaf.encoding=UTF-8 spring.thymeleaf.servlet.content-type=text/html #緩存設置爲false, 這樣修改以後立刻生效,便於調試 spring.thymeleaf.cache=false #數據庫 spring.datasource.url=jdbc:mysql://127.0.0.1:3306/testdb?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.jpa.properties.hibernate.hbm2ddl.auto=update #顯示SQL語句 spring.jpa.show-sql=true #不加下面這句則不會默認建立MyISAM引擎的數據庫 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect #本身重寫的配置類,默認使用utf8編碼 spring.jpa.properties.hibernate.dialect=com.wmyskxz.demo.shiro.config.MySQLConfig
新建一個【entity】包,在下面建立如下實體:
用戶信息:
@Entity public class UserInfo { @Id @GeneratedValue private Long id; // 主鍵. @Column(unique = true) private String username; // 登陸帳戶,惟一. private String name; // 名稱(匿名或真實姓名),用於UI顯示 private String password; // 密碼. private String salt; // 加密密碼的鹽 @JsonIgnoreProperties(value = {"userInfos"}) @ManyToMany(fetch = FetchType.EAGER) // 當即從數據庫中進行加載數據 @JoinTable(name = "SysUserRole", joinColumns = @JoinColumn(name = "uid"), inverseJoinColumns = @JoinColumn(name = "roleId")) private List<SysRole> roles; // 一個用戶具備多個角色 /** getter and setter */ }
角色信息:
@Entity public class SysRole { @Id @GeneratedValue private Long id; // 主鍵. private String name; // 角色名稱,如 admin/user private String description; // 角色描述,用於UI顯示 // 角色 -- 權限關係:多對多 @JsonIgnoreProperties(value = {"roles"}) @ManyToMany(fetch = FetchType.EAGER) @JoinTable(name = "SysRolePermission", joinColumns = {@JoinColumn(name = "roleId")}, inverseJoinColumns = {@JoinColumn(name = "permissionId")}) private List<SysPermission> permissions; // 用戶 -- 角色關係:多對多 @JsonIgnoreProperties(value = {"roles"}) @ManyToMany @JoinTable(name = "SysUserRole", joinColumns = {@JoinColumn(name = "roleId")}, inverseJoinColumns = {@JoinColumn(name = "uid")}) private List<UserInfo> userInfos;// 一個角色對應多個用戶 /** getter and setter */ }
權限信息:
@Entity public class SysPermission { @Id @GeneratedValue private Long id; // 主鍵. private String name; // 權限名稱,如 user:select private String description; // 權限描述,用於UI顯示 private String url; // 權限地址. @JsonIgnoreProperties(value = {"permissions"}) @ManyToMany @JoinTable(name = "SysRolePermission", joinColumns = {@JoinColumn(name = "permissionId")}, inverseJoinColumns = {@JoinColumn(name = "roleId")}) private List<SysRole> roles; // 一個權限能夠被多個角色使用 /** getter and setter */ }
注意:這裏有一個坑,還纏了我蠻久感受,就是當咱們想要使用RESTful風格返回給前臺JSON數據的時候,這裏有一個關於多對多無限循環的坑,好比當咱們想要返回給前臺一個用戶信息時,因爲一個用戶擁有多個角色,一個角色又擁有多個權限,而權限跟角色也是多對多的關係,也就是形成了 查用戶→查角色→查權限→查角色→查用戶... 這樣的無限循環,致使傳輸錯誤,因此咱們根據這樣的邏輯在每個實體類返回JSON時使用了一個
@JsonIgnoreProperties
註解,來排除本身對本身無線引用的過程,也就是打斷這樣的無限循環。
根據以上的代碼會自動生成user_info(用戶信息表)、sys_role(角色表)、sys_permission(權限表)、sys_user_role(用戶角色表)、sys_role_permission(角色權限表)這五張表,爲了方便測試咱們給這五張表插入一些初始化數據:
INSERT INTO `user_info` (`id`,`name`,`password`,`salt`,`username`) VALUES (1, '管理員','951cd60dec2104024949d2e0b2af45ae', 'xbNIxrQfn6COSYn1/GdloA==', 'wmyskxz'); INSERT INTO `sys_permission` (`id`,`description`,`name`,`url`) VALUES (1,'查詢用戶','userInfo:view','/userList'); INSERT INTO `sys_permission` (`id`,`description`,`name`,`url`) VALUES (2,'增長用戶','userInfo:add','/userAdd'); INSERT INTO `sys_permission` (`id`,`description`,`name`,`url`) VALUES (3,'刪除用戶','userInfo:delete','/userDelete'); INSERT INTO `sys_role` (`id`,`description`,`name`) VALUES (1,'管理員','admin'); INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (1,1); INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (2,1); INSERT INTO `sys_user_role` (`role_id`,`uid`) VALUES (1,1);
新建一個【config】包,在下面建立如下文件:
MySQLConfig:
public class MySQLConfig extends MySQL5InnoDBDialect { @Override public String getTableTypeString() { return "ENGINE=InnoDB DEFAULT CHARSET=utf8"; } }
這個文件關聯的是配置文件中最後一個配置,是讓 Hibernate 默認建立 InnoDB 引擎並默認使用 utf-8 編碼
MyShiroRealm:
public class MyShiroRealm extends AuthorizingRealm { @Resource private UserInfoService userInfoService; @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { // 能進入這裏說明用戶已經經過驗證了 UserInfo userInfo = (UserInfo) principalCollection.getPrimaryPrincipal(); SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); for (SysRole role : userInfo.getRoles()) { simpleAuthorizationInfo.addRole(role.getName()); for (SysPermission permission : role.getPermissions()) { simpleAuthorizationInfo.addStringPermission(permission.getName()); } } return simpleAuthorizationInfo; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { // 獲取用戶輸入的帳戶 String username = (String) authenticationToken.getPrincipal(); System.out.println(authenticationToken.getPrincipal()); // 經過username從數據庫中查找 UserInfo 對象 // 實際項目中,這裏能夠根據實際狀況作緩存,若是不作,Shiro本身也是有時間間隔機制,2分鐘內不會重複執行該方法 UserInfo userInfo = userInfoService.findByUsername(username); if (null == userInfo) { return null; } SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo( userInfo, // 用戶名 userInfo.getPassword(), // 密碼 ByteSource.Util.bytes(userInfo.getSalt()), // salt=username+salt getName() // realm name ); return simpleAuthenticationInfo; } }
自定義的 Realm ,方法跟上面的認證受權過程一致
ShiroConfig:
@Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { System.out.println("ShiroConfiguration.shirFilter()"); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); // 攔截器. Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); // 配置不會被攔截的連接 順序判斷 filterChainDefinitionMap.put("/static/**", "anon"); // 配置退出 過濾器,其中的具體的退出代碼Shiro已經替咱們實現了 filterChainDefinitionMap.put("/logout", "logout"); // <!-- 過濾鏈定義,從上向下順序執行,通常將/**放在最爲下邊 -->:這是一個坑呢,一不當心代碼就很差使了; // <!-- authc:全部url都必須認證經過才能夠訪問; anon:全部url都均可以匿名訪問--> filterChainDefinitionMap.put("/**", "authc"); // 若是不設置默認會自動尋找Web工程根目錄下的"/login.jsp"頁面 shiroFilterFactoryBean.setLoginUrl("/login"); // 登陸成功後要跳轉的連接 shiroFilterFactoryBean.setSuccessUrl("/index"); //未受權界面; shiroFilterFactoryBean.setUnauthorizedUrl("/403"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } /** * 憑證匹配器 * (因爲咱們的密碼校驗交給Shiro的SimpleAuthenticationInfo進行處理了) * * @return */ @Bean public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashAlgorithmName("md5"); // 散列算法:這裏使用MD5算法; hashedCredentialsMatcher.setHashIterations(2); // 散列的次數,好比散列兩次,至關於 md5(md5("")); return hashedCredentialsMatcher; } @Bean public MyShiroRealm myShiroRealm() { MyShiroRealm myShiroRealm = new MyShiroRealm(); myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); return myShiroRealm; } @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myShiroRealm()); return securityManager; } /** * 開啓shiro aop註解支持. * 使用代理方式;因此須要開啓代碼支持; * * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } @Bean(name = "simpleMappingExceptionResolver") public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() { SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver(); Properties mappings = new Properties(); mappings.setProperty("DatabaseException", "databaseError"); // 數據庫異常處理 mappings.setProperty("UnauthorizedException", "403"); r.setExceptionMappings(mappings); // None by default r.setDefaultErrorView("error"); // No default r.setExceptionAttribute("ex"); // Default is "exception" //r.setWarnLogCategory("example.MvcLogger"); // No default return r; } }
Apache Shiro 的核心經過 Filter 來實現,就好像 SpringMvc 經過 DispachServlet 來主控制同樣。 既然是使用 Filter 通常也就能猜到,是經過URL規則來進行過濾和權限校驗,因此咱們須要定義一系列關於URL的規則和訪問權限。
Filter Chain定義說明:
Shiro內置的FilterChain
Filter Name | Class |
---|---|
anon | org.apache.shiro.web.filter.authc.AnonymousFilter |
authc | org.apache.shiro.web.filter.authc.FormAuthenticationFilter |
authcBasic | org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
perms | org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter |
port | org.apache.shiro.web.filter.authz.PortFilter |
rest | org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter |
roles | org.apache.shiro.web.filter.authz.RolesAuthorizationFilter |
ssl | org.apache.shiro.web.filter.authz.SslFilter |
user | org.apache.shiro.web.filter.authc.UserFilter |
新建【dao】包,在下面建立【UserInfoDao】接口:
public interface UserInfoDao extends JpaRepository<UserInfo, Long> { /** 經過username查找用戶信息*/ public UserInfo findByUsername(String username); }
新建【service】包,建立【UserInfoService】接口:
public interface UserInfoService { /** 經過username查找用戶信息;*/ public UserInfo findByUsername(String username); }
並在該包下再新建一個【impl】包,新建【UserInfoServiceImpl】實現類:
@Service public class UserInfoServiceImpl implements UserInfoService { @Resource UserInfoDao userInfoDao; @Override public UserInfo findByUsername(String username) { return userInfoDao.findByUsername(username); } }
新建【controller】包,而後在下面建立如下文件:
HomeController:
@Controller public class HomeController { @RequestMapping({"/","/index"}) public String index(){ return"/index"; } @RequestMapping("/login") public String login(HttpServletRequest request, Map<String, Object> map) throws Exception{ System.out.println("HomeController.login()"); // 登陸失敗從request中獲取shiro處理的異常信息。 // shiroLoginFailure:就是shiro異常類的全類名. String exception = (String) request.getAttribute("shiroLoginFailure"); System.out.println("exception=" + exception); String msg = ""; if (exception != null) { if (UnknownAccountException.class.getName().equals(exception)) { System.out.println("UnknownAccountException -- > 帳號不存在:"); msg = "UnknownAccountException -- > 帳號不存在:"; } else if (IncorrectCredentialsException.class.getName().equals(exception)) { System.out.println("IncorrectCredentialsException -- > 密碼不正確:"); msg = "IncorrectCredentialsException -- > 密碼不正確:"; } else if ("kaptchaValidateFailed".equals(exception)) { System.out.println("kaptchaValidateFailed -- > 驗證碼錯誤"); msg = "kaptchaValidateFailed -- > 驗證碼錯誤"; } else { msg = "else >> "+exception; System.out.println("else -- >" + exception); } } map.put("msg", msg); // 此方法不處理登陸成功,由shiro進行處理 return "/login"; } @RequestMapping("/403") public String unauthorizedRole(){ System.out.println("------沒有權限-------"); return "403"; } }
這裏邊的地址對應咱們在設置 Shiro 時設置的地址
UserInfoController:
@RestController public class UserInfoController { @Resource UserInfoService userInfoService; /** * 按username帳戶從數據庫中取出用戶信息 * * @param username 帳戶 * @return */ @GetMapping("/userList") @RequiresPermissions("userInfo:view") // 權限管理. public UserInfo findUserInfoByUsername(@RequestParam String username) { return userInfoService.findByUsername(username); } /** * 簡單模擬從數據庫添加用戶信息成功 * * @return */ @PostMapping("/userAdd") @RequiresPermissions("userInfo:add") public String addUserInfo() { return "addUserInfo success!"; } /** * 簡單模擬從數據庫刪除用戶成功 * * @return */ @DeleteMapping("/userDelete") @RequiresPermissions("userInfo:delete") public String deleteUserInfo() { return "deleteUserInfo success!"; } }
新建三個頁面用來測試:
index.html:首頁
<!DOCTYPE html> <head> <meta charset="UTF-8"> <title>首頁</title> </head> <body> index - 首頁 </body> </html>
login.html:登陸頁
<!DOCTYPE html> <html xmlns:th="http://www.w3.org/1999/xhtml"> <head> <meta charset="UTF-8"> <title>登陸頁</title> </head> <body> 錯誤信息:<h4 th:text="${msg}"></h4> <form action="" method="post"> <p>帳號:<input type="text" name="username" value="wmyskxz"/></p> <p>密碼:<input type="text" name="password" value="123456"/></p> <p><input type="submit" value="登陸"/></p> </form> </body> </html>
403.html:沒有權限的頁面
<!DOCTYPE html> <head> <meta charset="UTF-8"> <title>403錯誤頁</title> </head> <body> 錯誤頁面 </body> </html>
http://localhost:8080/userList?username=wmyskxz
頁面,因爲沒有登陸就會跳轉到咱們配置好的http://localhost:8080/login
頁面。登錄以後就會看到正確返回的JSON數據,上面這些操做時候觸發MyShiroRealm.doGetAuthenticationInfo()
這個方法,也就是登陸認證的方法。http://localhost:8080/userAdd
頁面,由於咱們在數據庫中提早配置好了權限,可以看到正確返回的數據,可是咱們訪問http://localhost:8080/userDelete
時,就會返回錯誤頁面.注意:以上測試須要在REST工具中測試,由於在Controller層中配置了方法,你們也能夠不用REST風格來測試一下看看!
完成了以上的學習,咱們就差很少對 Shiro 框架有了必定了解了,更多的東西之後再分享再學習吧.
參考資料:
springboot(十四):springboot整合shiro-登陸認證和權限管理——純潔的微笑
Shiro安全框架入門 - 慕課視頻教程
Shiro 系列教程 —— how2j網站
歡迎轉載,轉載請註明出處!
簡書ID:@我沒有三顆心臟
github:wmyskxz 歡迎關注公衆微信號:wmyskxz 分享本身的學習 & 學習資料 & 生活 想要交流的朋友也能夠加qq羣:3382693