<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                # Shiro 安全模塊 [TOC] 1. 準備 SpringMVC 開發環境; 2. 引入 Shiro 依賴; 3. 配置 ShiroConfig 類; 4. 配置自定義 Realm; 5. 登陸驗證; 6. 注冊驗證; ## 準備 SpringMVC 開發環境 準備數據庫用戶表,存放用戶信息。 ```sql -- ---------------------------- -- Table structure for user_t -- ---------------------------- DROP TABLE IF EXISTS `user_t`; CREATE TABLE `user_t` ( `id` varchar(32) NOT NULL, `username` varchar(64) NOT NULL, `password` varchar(64) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; SET FOREIGN_KEY_CHECKS=1; ``` 編寫 DAO 接口,用于根據 Username 查詢信息以及插入數據。 userMapper.java ```java @Repository public interface UserMapper { /** * 根據用戶名查詢用戶信息 * @param username 用戶名 * @return 將數據封裝到Map類型中 */ public Map<String, Object> queryInfoByUsername(String username); /** * 插入一條數據 * @param data Map中包含id,username,password */ public void insertData(Map<String, String> data); } ``` userMapper.xml ```xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.myz.shirodemo.dao.UserMapper"> <select id="queryInfoByUsername" parameterType="java.lang.String" resultType="java.util.Map"> SELECT id, username, password FROM user_t WHERE username = #{username,jdbcType=VARCHAR} </select> <insert id="insertData" parameterType="java.util.Map"> INSERT INTO user_t ( id, username,password ) VALUES ( #{id, jdbcType=VARCHAR}, #{username, jdbcType=VARCHAR},#{password, jdbcType=VARCHAR}); </insert> </mapper> ``` ## 引入 Shiro 依賴 ```xml <!-- shiro --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-all</artifactId> <version>1.2.2</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.2.2</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.2.2</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.2.2</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.2</version> </dependency> <!-- shiro END--> ``` ## 配置 ShiroConfig 類 ```java @Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); // 攔截器。匹配原則是最上面的最優先匹配 Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>(); // 配置不會被攔截的鏈接 filterChainDefinitionMap.put("/login", "anon"); filterChainDefinitionMap.put("/doLogin", "anon"); filterChainDefinitionMap.put("/doRegister", "anon"); filterChainDefinitionMap.put("/register", "anon"); // 配置退出 過濾器,其中的具體的退出代碼Shiro已經替我們實現了 filterChainDefinitionMap.put("/doLogout", "logout"); // 剩余請求需要身份認證 filterChainDefinitionMap.put("/**", "authc"); // 如果不設置默認會自動尋找Web工程根目錄下的"/login.jsp"頁面 shiroFilterFactoryBean.setLoginUrl("/login"); // 未授權界面; // shiroFilterFactoryBean.setUnauthorizedUrl("/403"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } @Bean(name = "myShiroRealm") public ShiroRealm myShiroRealm(HashedCredentialsMatcher matcher){ ShiroRealm myShiroRealm = new ShiroRealm(); myShiroRealm.setCredentialsMatcher(matcher); return myShiroRealm; } @Bean public SecurityManager securityManager(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher matcher){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myShiroRealm(matcher)); return securityManager; } /** * 密碼匹配憑證管理器 * * @return */ @Bean(name = "hashedCredentialsMatcher") public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); // 采用MD5方式加密 hashedCredentialsMatcher.setHashAlgorithmName("MD5"); // 設置加密次數 hashedCredentialsMatcher.setHashIterations(1024); return hashedCredentialsMatcher; } } ``` 1. `shirFilter`( `SecurityManagersecurityManager` ) 方法,是設置 `shiro` 的過濾規則。用于控制哪些請求需要身份認證后才能繼續執行,哪些不需要認證等。 身份驗證相關: - authc:基于表單的攔截器;如“/**=authc”,如果沒有登錄會跳到相應的登錄頁面登錄;主要屬性:usernameParam:表單提交的用戶名參數名( username); passwordParam:表單提交的密碼參數名(password); rememberMeParam:表單提交的密碼參數名(rememberMe); loginUrl:登錄頁面地址(/login.jsp);successUrl:登錄成功后的默認重定向地址;failureKeyAttribute:登錄失敗后錯誤信息存儲 key(shiroLoginFailure); - authcBasic:Basic HTTP 身份驗證攔截器,主要屬性: applicationName:彈出登錄框顯示的信息(application); - logout:退出攔截器,主要屬性:redirectUrl:退出成功后重定向的地址(/); 示例“/logout=logout” - user:用戶攔截器,用戶已經身份驗證 / 記住我登錄的都可;示例“/**=user” - anon:匿名攔截器,即不需要登錄即可訪問;一般用于靜態資源過濾;示例“/static/**=anon” 授權相關的: - roles:角色授權攔截器,驗證用戶是否擁有所有角色;主要屬性: loginUrl:登錄頁面地址(/login.jsp);unauthorizedUrl:未授權后重定向的地址;示例“/admin/**=roles[admin]” - perms:權限授權攔截器,驗證用戶是否擁有所有權限;屬性和 roles 一樣;示例“/user/**=perms[“user:create”]” - port:端口攔截器,主要屬性:port(80):可以通過的端口;示例“/test= port[80]”,如果用戶訪問該頁面是非 80,將自動將請求端口改為 80 并重定向到該 80 端口,其他路徑 / 參數等都一樣 - rest:rest 風格攔截器,自動根據請求方法構建權限字符串(GET=read, POST=create,PUT=update,DELETE=delete,HEAD=read,TRACE=read,OPTIONS=read, MKCOL=create)構建權限字符串;示例“/users=rest[user]”,會自動拼出“user:read,user:create,user:update,user:delete”權限字符串進行權限匹配(所有都得匹配,isPermittedAll); - ssl:SSL 攔截器,只有請求協議是 https 才能通過;否則自動跳轉會 https 端口(443);其他和 port 攔截器一樣; - noSessionCreation:不創建會話攔截器,調用 subject.getSession(false) 不會有什么問題,但是如果 subject.getSession(true) 將拋出 DisabledSessionException 異常; 2. `myShiroRealm`(`HashedCredentialsMatchermatcher`) 用于配置自定義的 `Realm` 。在 `Shiro` 中,所有有關身份認證及授權管理數據源的獲取與管理,都在 `Realm` 中進行。 3. `hashedCredentialsMatcher()` 用于生成加密規則。這里采用 `MD5` 加密 `1024` 次的方式對密碼進行加密處理。 4. `securityManager( `HashedCredentialsMatchermatcher` )` 將加密規則屬性設置到自定義的 `ShiroRealm` 中,并將這個 `Realm` 加載到 `SecurityManager` 中。 ## 配置自定義 Realm ```java public class ShiroRealm extends AuthenticatingRealm { @Autowired private BaseService baseService; private SimpleAuthenticationInfo info = null; /** * 1.doGetAuthenticationInfo,獲取認證消息,如果數據庫中沒有數,返回null,如果得到了正確的用戶名和密碼, * 返回指定類型的對象 * * 2.AuthenticationInfo 可以使用SimpleAuthenticationInfo實現類,封裝正確的用戶名和密碼。 * * 3.token參數 就是我們需要認證的token * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { // 將token裝換成UsernamePasswordToken UsernamePasswordToken upToken = (UsernamePasswordToken) authenticationToken; // 獲取用戶名即可 String username = upToken.getUsername(); // 查詢數據庫,是否查詢到用戶名和密碼的用戶 Map<String, Object> userInfo = baseService.queryInfoByUsername(username); if(userInfo != null) { // 如果查詢到了,封裝查詢結果,返回給我們的調用 Object principal = userInfo.get("username"); Object credentials = userInfo.get("password"); // 獲取鹽值,即用戶名 ByteSource salt = ByteSource.Util.bytes(username); String realmName = this.getName(); // 將賬戶名,密碼,鹽值,realmName實例化到SimpleAuthenticationInfo中交給Shiro來管理 info = new SimpleAuthenticationInfo(principal, credentials, salt,realmName); }else { // 如果沒有查詢到,拋出一個異常 throw new AuthenticationException(); } return info; } } ``` 1. 這里我只做了身份認證。新建一個 `ShiroRealm` 類繼承 `AuthenticatingRealm` 類,實現 `doGetAuthenticationInfo( AuthenticationTokenauthenticationToken` ) 方法。 2. 這個方法主要就是用于獲取數據庫中的賬戶信息,以便用于和用戶登錄時從前臺傳過來的賬戶密碼進行對比。 3. 根據用戶名到用戶表中查詢賬戶名密碼,并設置好鹽值。這里的鹽值要和 `ShiroConfig` 中的鹽值規則一樣。將賬戶名,密碼,鹽值, `realmName` 實例化到 `SimpleAuthenticationInfo` 中交給 `Shiro` 來管理。 4. 如果賬戶不存在,則拋出 `AuthenticationException` 異常。 5. 這樣,每次用戶進行 `login` 操作時,就會調用 `doGetAuthenticationInfo` 方法。 `Shiro` 就自動幫我們校驗了賬戶密碼是否匹配。 ## 登陸驗證 ```java @Controller public class MyController { @Autowired private BaseService baseService; private final Logger logger = LoggerFactory.getLogger(MyController.class); @RequestMapping("/doLogin") public String doLogin(@RequestParam("username") String username, @RequestParam("password") String password) { // 創建Subject實例 Subject currentUser = SecurityUtils.getSubject(); // 將用戶名及密碼封裝到UsernamePasswordToken UsernamePasswordToken token = new UsernamePasswordToken(username, password); try { currentUser.login(token); // 判斷當前用戶是否登錄 if (currentUser.isAuthenticated() == true) { return "/index.html"; } } catch (AuthenticationException e) { e.printStackTrace(); System.out.println("登錄失敗"); } return "/loginPage.html"; } @RequestMapping("/doRegister") public String doRegister(@RequestParam("username") String username, @RequestParam("password") String password) { boolean result = baseService.registerData(username,password); if(result){ return "/login"; } return "/register"; } @RequestMapping(value = "/login") public String login() { logger.info("login() 方法被調用"); return "loginPage.html"; } @RequestMapping(value = "/register") public String register() { logger.info("register() 方法被調用"); return "registerPage.html"; } @RequestMapping(value = "/hello") public String hello() { logger.info("hello() 方法被調用"); return "helloPage.html"; } } ``` 1. 在 `doLogin` 方法中,實現登錄認證過程。 2. 首先獲取當前 `Subject` 實例 3. 將用戶名和密碼封裝到 `UsernamePasswordToken` 中 4. 用當前 `Subject` 實例執行 `login` 方法,傳入參數為剛剛封裝的 `token` 。執行 `login` 方法后, `shiro` 框架最終就會調用剛剛自定義 `ShiroRealm` 中的 `doGetAuthenticationInfo` 方法。 5. 用 `isAuthenticated()` 方法判斷用戶是否已經登錄,如果是則跳轉到登錄后的頁面(這里我跳轉到的是 `index.html`)。如果登錄失敗,則走報異常,最后還是跳轉到登錄界面。 6. 這里我只 `catch` 了 `AuthenticationException` 異常。然而在 `AuthenticationException` 下有多個子異常,用于各種登錄失敗的場景,比如賬戶名不存在,密碼不對,登錄次數過多等等。大家針對不同的情況做不同的處理。但有一點建議,就是對于前臺用戶來說,不要暴露過多的錯誤信息,只是報一個登錄失敗即可,提高安全性。 ## 注冊驗證 在 `service` 中對 `DAO` 進行封裝,實現信息查詢以及信息注冊。 ``` public interface BaseService { /** * 根據用戶名查詢用戶信息 * @param username 用戶名 * @return 將數據封裝到Map類型中 */ public Map<String, Object> queryInfoByUsername(String username); /** * 注冊功能 * @param username 用戶名 * @param password 密碼 * @return */ public boolean registerData(String username, String password); } ``` ## MD5 加密 ```java @Service public class BaseServiceImpl implements BaseService { @Autowired private UserMapper userMapper; @Override public Map<String, Object> queryInfoByUsername(String username) { return userMapper.queryInfoByUsername(username); } @Override public boolean registerData(String username, String password) { // 生成uuid String id = UUIDUtil.getOneUUID(); // 將用戶名作為鹽值 ByteSource salt = ByteSource.Util.bytes(username); /* * MD5加密: * 使用SimpleHash類對原始密碼進行加密。 * 第一個參數代表使用MD5方式加密 * 第二個參數為原始密碼 * 第三個參數為鹽值,即用戶名 * 第四個參數為加密次數 * 最后用toHex()方法將加密后的密碼轉成String * */ String newPs = new SimpleHash("MD5", password, salt, 1024).toHex(); Map<String, String> dataMap = new HashMap<>(); dataMap.put("id", id); dataMap.put("username", username); dataMap.put("password", newPs); // 看數據庫中是否存在該賬戶 Map<String, Object> userInfo = queryInfoByUsername(username); if(userInfo == null) { userMapper.insertData(dataMap); return true; } return false; } } ``` 1. 注冊時注意,由于之前配置了鹽值規則及加密規則,所以這里要對用戶輸入的密碼也做相同的處理之后再存入數據庫中。 2. 使用 `SimpleHash` 類完成密碼的加密。最后用 `toHex()` 將加密后的密碼轉成 `String` 。 ## Shiro 緩存配置 ```xml <?xml version="1.0" encoding="UTF-8"?> <ehcache updateCheck="false" name="shirocache"> <diskStore path="java.io.tmpdir"/> <!-- 登錄記錄緩存 鎖定10分鐘 --> <cache name="passwordRetryCache" maxEntriesLocalHeap="2000" eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="0" overflowToDisk="false" statistics="true"> </cache> <cache name="authorizationCache" maxEntriesLocalHeap="2000" eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="0" overflowToDisk="false" statistics="true"> </cache> <cache name="authenticationCache" maxEntriesLocalHeap="2000" eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="0" overflowToDisk="false" statistics="true"> </cache> <cache name="shiro-activeSessionCache" maxEntriesLocalHeap="2000" eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="0" overflowToDisk="false" statistics="true"> </cache> <cache name="shiro_cache" maxElementsInMemory="2000" maxEntriesLocalHeap="2000" eternal="false" timeToIdleSeconds="0" timeToLiveSeconds="0" maxElementsOnDisk="0" overflowToDisk="true" memoryStoreEvictionPolicy="FIFO" statistics="true"> </cache> </ehcache> ```
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看