javascript
SpringBoot整合Shiro搭建登录注册认证授权权限项目模板
主要內容:
1 SpringBoot整合Shiro安全框架;
2 Shiro主要學習內容總結;(執行流程、主要對象接口、注意事項等)
3 Redis實現對權限信息緩存;
! 溫馨提示: 想要快速搭Shiro整合項目,請跳至SpringBoot整合Shiro
1 Shiro 介紹
https://baike.baidu.com/item/shiro/17753571
2 主要接口和對象
2.1 Subject 接口
此接口為簡單理解為主體,外部應用想要訪問我們的服務器時,如果我們使用了Shiro作為安全框架,那么我們的應用就需要與Subject進行交互,Subject會記錄我們應用或者用戶(可以簡單理解為一個請求主體)的信息,它是一個接口,其中提供了認證和授權的一些方法.(登錄/登出的方法)
我的理解,Subject就相當于一個公司的"HR",她會將"面試者"(應用/用戶)的信息和需求記錄下來,交給下"領導層".并告訴領導面試者想要執行的操作(登錄或者退出)
2.2 SecurityManager 接口
字面意思: 安全管理器;
SecurityManager是一個核心接口,他就是Subject的"領導層",SecurityManager他會對Subject進行管理,此接口可以完成Subject的認證和授權等操作.但其實他是繼承了Authenticator(認證接口)和Authorizer(授權接口)和SessionManager(session管理器)三個接口,所以具體的認證和授權流程還是得這三個接口的具體實現來完成.
2.2.1 Authenticator
認證器:
Authenticator是一個接口,shiro提供ModularRealmAuthenticator實現類,次實現類,通過ModularRealmAuthenticator基本上可以滿足大多數需求,主要用于應用或者用戶的認證.
2.2.2 Authorizer
授權器:
用戶通過認證器認證通過,在訪問功能時需要通過授權器判斷用戶是否有此功能的操作權限。
2.2.3 Realm
字面意思: 區域/領域
這里的Realm有點特殊,他在SecurityManager接口中沒有以任何形式體現出來,但是我們的認證器Authenticator 和授權器Authorizer,卻需要Realm來完成具體認證或者授權數據的具體獲取.
Realm是一個接口,其實現子類中有這樣幾個實現類尤為重要:
AuthenticatingRealm(專門用于認證的realm)、
AuthorizingRealm(專門用于授權的realm)、
SimpleAccountRealm(此realm是上面兩個的子類)
如下是SecurityManager、Authenticator、 Authorizer 和這集合Realm之間的關系圖:
2.2.4 SessionManager
會話管理器:
SessionManager也是一個接口,shiro框架定義了一套會話管理,它不依賴web容器的session,所以shiro可以使用在非web應用上,也可以將分布式應用的會話集中在一點管理,此特性可使它實現單點登錄。
2.2.5 SessionDAO
SessionDAO即會話dao,
是對session會話操作的一套接口,比如要將session存儲到數據庫,可以通過jdbc將會話存儲到數據庫.
2.2.6 CacheManager
CacheManager即緩存管理,將用戶權限數據存儲在緩存,這樣可以提高性能.
整合中我們會使用到兩種類型的緩存方式都會用到CacheManager;
2.2.7 Cryptography
Cryptography即密碼管理,shiro提供了一套加密/解密的組件,方便開發。比如提供常用的散列、加/解密等功能.
2.3 小結
以上介紹的Shiro接口和實現類,都是核心用到的,其實知道了那樣幾個借口和實現類干嘛用的我們對shiro執行的大致流程心里也有譜了;
Shiro官網貼出Shiro核心構造圖解如下:
3 Shiro與Springboot的整合
主要內容:
1 Shiro的具體執行流程;
2 Shiro的整合思路
3 Shiro與SpringBoot的具體整合代碼實現
4 具體實現中需要注意的事項
3.1 Shiro執行的大致流程
其實通過在Shiro的具體介紹中也能大概清楚了執行流程,這里以用戶為請求為例來進行具體梳理依稀流程:
1) 當用戶請求進來時,會攜帶具體的用戶信息(用戶名密碼等);
2) 請求進來這時候就需要Subject對用戶的具體信息進行封裝到Token中(我們后期會用到UsernamePasswordToken 很見名知意吧~~~)
3) 這時候SecurityManager就會擔起任務來管理我們的Subject來完成操作;
4) SecurityManager拿到后,通過認證器去執行認證流程,認證器的具體認證流程就需要我們使用認證有關的Realm來查詢數據庫完成數據對比來實現認證;
5) 當認證通過后,接下來就是具體的授權了,這時候,SecurityManager就會通過授權器來完成授權操作,這里校驗用戶權限的方式可能是多樣的,也許是配置文件中已經配置,但大部分情況下也需要查詢用戶,角色和權限表來給用戶來完成具體的授權;當然,授權這部分,授權器也會通過具體的授權Realm來完成授權操作;
6) 授權完成要我們就能根據權限來訪問可見頁面或者執行具體操作了
補充:
通過上面流程可以很容易猜到:
1 查詢數據庫中用戶信息和權限信息的操作就需要在Realm中來完成;查詢操作就需要我們定義Service來查詢,所以我們不能使用默認的Realm來操作,我們需要自定義Realm來加入我們自己的邏輯,和需求;
2 查詢出來數據的緩存也需要在Realm中完成(因為數據是在Realm中查詢出來的)
所以我們需要在Realm中配置開啟緩存,讓Shiro來完成數據的緩存
3 具體使用Redis來緩存還是使用Shiro自帶的緩存來事項就看我們怎么配置了,反正都需要使用到Shiro的CacheManager (如使用Redis就需要自定緩存管理器時實現此接口)
4 從介紹SecurityManager可以看到SimpleAccountRealm是AuthenticatingRealm(專門用于認證的realm)、AuthorizingRealm(專門用于授權的realm)的子類,所以我們在自定義Realm時就可以繼承SimpleAccountRealm 來覆蓋其中認證和授權相關方法即可完成從數據庫中查詢數據完成認證授權的先關操作.
3.2 Shiro與SpringBoot的整合思路
1 導入相關依賴和插件(SpringBoot的依賴就不用說了,最起碼我們目前知道的Redis依賴、Shiro依賴、數據庫相關依賴(mysql驅動程序包,連接池等)、Mybatis依賴等等)
2 創建數據庫: 用戶,角色,權限一般這三張表不會少的;
3 yml中配置: 一般需要配置端口號、應用名、數據源、連接池(c3p0,druid等)、Redis等
4 準備工作結束,接下來就是具體的代碼實現和實現過程中注意事項了
3.3 具體整合實現代碼(只板書主要流程代碼)
如果使用的是Maven使用的依賴如下:(有些是我后期加上的,為了在整合日志等)
pom.xml如下
數據庫sql如下:
/* SQLyog Enterprise v12.09 (64 bit) MySQL - 5.5.40 : Database - shiro ********************************************************************* *//*!40101 SET NAMES utf8 */;/*!40101 SET SQL_MODE=''*/;/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; CREATE DATABASE /*!32312 IF NOT EXISTS*/`shiro` /*!40100 DEFAULT CHARACTER SET utf8 */;/*Table structure for table `t_perms` */DROP TABLE IF EXISTS `t_perms`;CREATE TABLE `t_perms` (`id` int(6) NOT NULL AUTO_INCREMENT,`name` varchar(80) DEFAULT NULL,`url` varchar(255) DEFAULT NULL,PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;/*Data for the table `t_perms` */insert into `t_perms`(`id`,`name`,`url`) values (1,'user:*:*',''),(2,'product:*:01',NULL),(3,'order:*:*',NULL);/*Table structure for table `t_role` */DROP TABLE IF EXISTS `t_role`;CREATE TABLE `t_role` (`id` int(6) NOT NULL AUTO_INCREMENT,`name` varchar(60) DEFAULT NULL,PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;/*Data for the table `t_role` */insert into `t_role`(`id`,`name`) values (1,'admin'),(2,'user'),(3,'product');/*Table structure for table `t_role_perms` */DROP TABLE IF EXISTS `t_role_perms`;CREATE TABLE `t_role_perms` (`id` int(6) NOT NULL,`roleid` int(6) DEFAULT NULL,`permsid` int(6) DEFAULT NULL,PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;/*Data for the table `t_role_perms` */insert into `t_role_perms`(`id`,`roleid`,`permsid`) values (1,1,1),(2,1,2),(3,2,1),(4,3,2),(5,1,3);/*Table structure for table `t_user` */DROP TABLE IF EXISTS `t_user`;CREATE TABLE `t_user` (`id` int(6) NOT NULL AUTO_INCREMENT,`username` varchar(40) DEFAULT NULL,`password` varchar(40) DEFAULT NULL,`salt` varchar(255) DEFAULT NULL,PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;/*Data for the table `t_user` */insert into `t_user`(`id`,`username`,`password`,`salt`) values (1,'xiaochen','24dce55acdcb3b6363c7eacd24e98cb7','28qr0xu%'),(2,'zhangsan','ca9f1c951ce2bfb5669f3723780487ff','IWd1)#or'),(4,'mrkay','da8a464db04099b5549594a1cc3c8e38','iqhEGj');/*Table structure for table `t_user_role` */DROP TABLE IF EXISTS `t_user_role`;CREATE TABLE `t_user_role` (`id` int(6) NOT NULL,`userid` int(6) DEFAULT NULL,`roleid` int(6) DEFAULT NULL,PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;/*Data for the table `t_user_role` */insert into `t_user_role`(`id`,`userid`,`roleid`) values (1,1,1),(2,2,2),(3,2,3),(4,4,2),(5,4,3),(6,4,1);/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;application.yml中配置如下:
server:port: 9999 # thymleaf spring:thymeleaf:prefix: classpath:/static/suffix: .html# 數據源datasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/shiro?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMTusername: rootpassword: ****druid:db-type:com.alibaba.druid.pool.DruidDataSource# 配置redis連接redis:host: 127.0.0.1port: 6379database: 0 # mybatis mybatis:mapper-locations: classpath:show/mrkay/dao/*.xml創建ShiroConfig.java用于配置我們的SecurityManager、Realm、ShiroFilterFactoryBean(過濾放行攔截路徑等)等等
package show.mrkay.config;import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.realm.Realm; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import show.mrkay.shiro.cache.RedisCacheManager; import show.mrkay.shiro.relms.Md5CustumRealm;import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map;/*** @ClassName: ShiroConfig* @description: Shiro配置類, 用于將shiro用到的安全管理器, 過濾器, realm交給IOC容器* @Author: MrKay* @Date: 2020/12/8*/ @Configuration public class ShiroConfig {/*** @MethodName: ShiroFilterFactoryBean* @Params: [securityManager]* @return: org.apache.shiro.spring.web.ShiroFilterFactoryBean* @Description: ShiroFilterFactoryBean 用于過濾URL請求路徑* @Author: MrKay* @Date: 2020/12/8*/@Beanpublic ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager) {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();//放行訪問路徑map.put("/login.html", "anon");map.put("/user/login", "anon");map.put("/user/register", "anon");map.put("/user/registerview", "anon");map.put("/user/getImage", "anon");//放行靜態資源map.put("/js/**", "anon");map.put("/css/**", "anon");map.put("/img/**", "anon");//攔截map.put("/**", "authc");//默認登錄請求路徑shiroFilterFactoryBean.setLoginUrl("/user/loginview");shiroFilterFactoryBean.setFilterChainDefinitionMap(map);return shiroFilterFactoryBean;}/*** @MethodName: DefaultSecurityManager* @Params: [realm]* @return: org.apache.shiro.mgt.DefaultSecurityManager* @Description: 注入安全管理器* @Author: MrKay* @Date: 2020/12/8*/@Beanpublic DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm) {DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();defaultSecurityManager.setRealm(realm);return defaultSecurityManager;}/*** @MethodName: Realm* @Params: []* @return: org.apache.shiro.realm.Realm* @Description: 注入自定義realm* @Author: MrKay* @Date: 2020/12/8*/@Beanpublic Realm getRealm() {Md5CustumRealm md5CustumRealm = new Md5CustumRealm();//修改憑證校驗匹配器HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();//設置使用MD5加密credentialsMatcher.setHashAlgorithmName("MD5");//設置散列次數credentialsMatcher.setHashIterations(1024);md5CustumRealm.setCredentialsMatcher(credentialsMatcher);//設置緩存管理器(shiro默認自帶緩存管理器)// md5CustumRealm.setCacheManager(new EhCacheManager());//使用redis自定義緩存管理器來緩存我們的數據md5CustumRealm.setCacheManager(new RedisCacheManager());md5CustumRealm.setCachingEnabled(true);//開啟全局緩存// 開啟認證緩存md5CustumRealm.setAuthenticationCachingEnabled(true);md5CustumRealm.setAuthenticationCacheName("authenticationCache");//開啟授權緩存md5CustumRealm.setAuthorizationCachingEnabled(true);md5CustumRealm.setAuthorizationCacheName("authorizationCache");return md5CustumRealm;}/*** @MethodName: getShiroDialect* @Params: []* @return: at.pollux.thymeleaf.shiro.dialect.ShiroDialect* @Description: 為了時Shiro標簽在html中生效需要使用shiro的方言才能使thymeleaf標簽生效* @Author: MrKay* @Date: 2020/12/13*/@Bean(name = "shiroDialect")public ShiroDialect getShiroDialect() {return new ShiroDialect();}}注意:上面文件中使用的工具類和自定義Redis緩存等,我會在后面附上代碼這里不再進行板書;
自定義Realm
package show.mrkay.shiro.relms;import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.pam.ModularRealmAuthenticator; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.SimpleAccountRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; import org.apache.shiro.util.CollectionUtils; import org.springframework.util.ObjectUtils; import show.mrkay.entity.Perms; import show.mrkay.entity.Role; import show.mrkay.entity.User; import show.mrkay.service.RoleService; import show.mrkay.service.UserService; import show.mrkay.shiro.salt.CustumByteSource; import show.mrkay.utils.ApplicationContextUtils;/*** @ClassName: CustumRealm* @description: 自定義Realm來來完成對用戶用戶信息的認證和授權(MD5加密)* @Author: MrKay* @Date: 2020/12/6*/ public class Md5CustumRealm extends SimpleAccountRealm {/*** @MethodName: doGetAuthorizationInfo* @Params: [principals]* @return: org.apache.shiro.authz.AuthorizationInfo* @Description: 授權* @Author: MrKay* @Date: 2020/12/6*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {System.out.println("==========START==================授權開始================START============");//獲取身份信息String primaryPrincipal = (String) principals.getPrimaryPrincipal();//獲取userServiceUserService userService = (UserService) ApplicationContextUtils.getObjectBName("userService");RoleService roleService = (RoleService) ApplicationContextUtils.getObjectBName("roleService");User rolesUser = userService.findRolesByUserName(primaryPrincipal);//授權if (!CollectionUtils.isEmpty(rolesUser.getRoles())) {SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();for (Role role : rolesUser.getRoles()) {//授權角色simpleAuthorizationInfo.addRole(role.getName());Role permsRole = roleService.findPermsByRoleId(role.getId());if (!CollectionUtils.isEmpty(permsRole.getPerms())) {for (Perms perm : permsRole.getPerms()) {//授權權限資源simpleAuthorizationInfo.addStringPermission(perm.getName());}}}System.out.println("==========END==================授權結束================END============");return simpleAuthorizationInfo;}return null;}/*** @MethodName: doGetAuthenticationInfo* @Params: [token]* @return: org.apache.shiro.authc.AuthenticationInfo* @Description: 認證* @Author: MrKay* @Date: 2020/12/6*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {System.out.println("==========START==================認證開始================START============");//獲取token中用戶輸入當然憑證String principal = (String) token.getPrincipal();//獲取UserService實例對象UserService userService = (UserService) ApplicationContextUtils.getObjectBName("userService");User findUser = userService.findByUserName(principal);if (!ObjectUtils.isEmpty(findUser)) {System.out.println("==========END==================認證結束================END============");return new SimpleAuthenticationInfo(findUser.getUsername(), findUser.getPassword(), new CustumByteSource(findUser.getSalt()), this.getName());}return null;} }同樣:上面代碼中使用到的工具類的會在文末附上代碼地址;
自定義RedisManager
package show.mrkay.shiro.cache;import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheException; import org.apache.shiro.cache.CacheManager;/*** @ClassName: RedisCacheManager* @description: Redis緩存管理器, 用于緩存用戶信息和權限信息,這里將我們自己的實現的redis緩存管理器* @Author: MrKay* @Date: 2020/12/17*/ public class RedisCacheManager implements CacheManager {/*** @MethodName: getCache* @Params: [cacheName] 認證或者授權緩存的統一名稱(AuthenticationInfo,AuthorizationInfo)* @return: org.apache.shiro.cache.Cache<K, V>* @Description:* @Author: MrKay* @Date: 2020/12/19*/@Overridepublic <K, V> Cache<K, V> getCache(String cacheName) throws CacheException {System.out.println("================" + cacheName + "======================");return new RedisCache<K, V>(cacheName);} }RedisCache.java
package show.mrkay.shiro.cache;import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheException; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.StringRedisSerializer; import show.mrkay.utils.ApplicationContextUtils;import java.util.Collection; import java.util.Set;/*** @ClassName: RedisCache* @description: 自定義redis緩存Cache* @Author: MrKay* @Date: 2020/12/17*/ public class RedisCache<Key, Value> implements Cache<Key, Value> {//創建成員變量private String cacheName;public RedisCache() {}public RedisCache(String cacheName) {this.cacheName = cacheName;}public String getCacheName() {return cacheName;}public void setCacheName(String cacheName) {this.cacheName = cacheName;}/*** @MethodName: get* @Params: [key]* @return: Value* @Description: 獲取緩存* @Author: MrKay* @Date: 2020/12/19*/@Overridepublic Value get(Key key) throws CacheException {//從redisSystem.out.println("======START============獲取緩存{cacheName:" + this.cacheName + ",鍵:" + key + "}=====================START========");RedisTemplate redisTemplate = getRedisTemplate();Value value = (Value) redisTemplate.opsForHash().get(this.cacheName, key.toString());System.out.println("======END============獲取緩存{cacheName:" + this.cacheName + ",鍵:" + key + "}=====================END========");return value;}/*** @MethodName: put* @Params: [key, value]* @return: Value* @Description: 存儲緩存* @Author: MrKay* @Date: 2020/12/19*/@Overridepublic Value put(Key key, Value value) throws CacheException {System.out.println("======START============設置緩存{cacheName:" + this.cacheName + ",鍵:" + key + ",值:" + value + "}=====================START========");getRedisTemplate().opsForHash().put(this.cacheName, key.toString(), value);System.out.println("======END============設置緩存{cacheName:" + this.cacheName + ",鍵:" + key + ",值:" + value + "}=====================END========");return null;}/*** @MethodName: remove* @Params: [key]* @return: Value* @Description: 刪除緩存* @Author: MrKay* @Date: 2020/12/19*/@Overridepublic Value remove(Key key) throws CacheException {System.out.println("======START============刪除指定緩存{cacheName:" + this.cacheName + ",鍵:" + key + "}=====================START========");getRedisTemplate().opsForHash().delete(this.cacheName, key.toString());System.out.println("======END============刪除指定緩存{cacheName:" + this.cacheName + ",鍵:" + key + "}=====================END========");return null;}/*** @MethodName: clear* @Params: []* @return: void* @Description: 清除緩存, 這里的情書是將所有緩存清除而不是某一個緩存* @Author: MrKay* @Date: 2020/12/19*/@Overridepublic void clear() throws CacheException {System.out.println("======START============清空所有緩存{cacheName:" + this.cacheName + "}=====================START========");getRedisTemplate().delete(this.cacheName);System.out.println("======END============清空所有緩存{cacheName:" + this.cacheName + "}=====================END========");}/*** @MethodName: size* @Params: []* @return: int* @Description: 返回緩存數量大小* @Author: MrKay* @Date: 2020/12/19*/@Overridepublic int size() {return getRedisTemplate().opsForHash().size(this.cacheName).intValue();}/*** @MethodName: keys* @Params: []* @return: java.util.Set<Key>* @Description: 返回緩存的key的去重集合* @Author: MrKay* @Date: 2020/12/19*/@Overridepublic Set<Key> keys() {return getRedisTemplate().opsForHash().keys(this.cacheName);}/*** @MethodName: values* @Params: []* @return: java.util.Collection<Value>* @Description: 返回所有值的集合* @Author: MrKay* @Date: 2020/12/19*/@Overridepublic Collection<Value> values() {return getRedisTemplate().opsForHash().values(this.cacheName);}/*** @MethodName: getRedisTemplate* @Params: []* @return: org.springframework.data.redis.core.RedisTemplate* @Description: 獲取redisTemplate用于操作redis;* @Author: MrKay* @Date: 2020/12/19*/private RedisTemplate getRedisTemplate() {RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getObjectBName("redisTemplate");redisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setHashKeySerializer(new StringRedisSerializer());return redisTemplate;} }主要Controller:
package show.mrkay.controller.sys;import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping;/*** @ClassName: IndexController* @description: 首頁Controller* @Author: MrKay* @Date: 2020/12/12*/ @Controller public class IndexController {@RequestMapping("/index")public String index() {return "index";} } package show.mrkay.controller.sys;import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import show.mrkay.entity.User; import show.mrkay.service.UserService; import show.mrkay.utils.VerifyCodeUtils;import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException;/*** @ClassName: UserController* @description: 用戶controller* @Author: MrKay* @Date: 2020/12/12*/ @Controller @RequestMapping("/user") public class UserController {@Autowiredprivate UserService userService;/*** @MethodName: register* @Params: []* @return: java.lang.String* @Description: 注冊頁面* @Author: MrKay* @Date: 2020/12/12*/@RequestMapping("/registerview")public String register() {return "register";}/*** @MethodName: loginview* @Params: []* @return: java.lang.String* @Description: 登錄頁面* @Author: MrKay* @Date: 2020/12/13*/@RequestMapping("/loginview")public String loginview() {return "login";}/*** @MethodName: getImage* @Params: [session, request, response]* @return: void* @Description: 獲取驗驗證碼請求路徑* @Author: MrKay* @Date: 2020/12/12*/@RequestMapping("/getImage")public void getImage(HttpSession session, HttpServletRequest request, HttpServletResponse response) throws IOException {String verifyCode = VerifyCodeUtils.generateVerifyCode(4);session.setAttribute("code", verifyCode);ServletOutputStream sos = response.getOutputStream();response.setContentType("image/png");VerifyCodeUtils.outputImage(100, 30, sos, verifyCode);}/*** @MethodName: register* @Params: []* @return: java.lang.String* @Description: 具體的注冊操作, 注冊成功跳轉到登錄頁面, 注冊失敗跳轉到注冊 頁面* @Author: MrKay* @Date: 2020/12/12*/@RequestMapping("/register")public String register(User user) {try {//注冊成功后跳轉到登錄頁面userService.register(user);return "redirect:/user/loginview";} catch (Exception e) {e.printStackTrace();return "redirect:/user/registerview";}}@RequestMapping("/login")public String login(@RequestParam(value = "username") String username, @RequestParam(value = "password") String password, @RequestParam String code, HttpSession session) {//從session中獲取驗證碼String varifyCode = (String) session.getAttribute("code");try {if (varifyCode.equalsIgnoreCase(code)) {//執行登錄的驗證Subject subject = SecurityUtils.getSubject();subject.login(new UsernamePasswordToken(username, password));//登錄成功重定向到index頁面return "redirect:/index";} else {throw new RuntimeException("驗證碼錯誤!");}} catch (UnknownAccountException e) {e.printStackTrace();System.out.println("用戶名錯誤!");} catch (IncorrectCredentialsException e) {e.printStackTrace();System.out.println("密碼錯誤!");} catch (Exception e) {e.printStackTrace();}return "redirect:/user/loginview";}/*** @MethodName: logout* @Params: []* @return: java.lang.String* @Description: 退出登錄* @Author: MrKay* @Date: 2020/12/12*/@RequestMapping("/logout")public String logout() {SecurityUtils.getSubject().logout();return "redirect:/user/loginview";}}登錄,注冊,首頁html頁面
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta charset="UTF-8"><meta name="viewport"content="width=device-width,mvcUser-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>用戶登錄</title><link rel="stylesheet" th:href="@{/css/style2.0.css}"><style type="text/css">ul li {font-size: 30px;color: #2ec0f6;}.tyg-div {z-index: -1000;float: left;position: absolute;left: 5%;top: 20%;}.tyg-p {font-size: 14px;font-family: 'microsoft yahei';position: absolute;top: 135px;left: 60px;}.tyg-div-denglv {z-index: 1000;float: right;position: absolute;right: 3%;top: 10%;}.tyg-div-form {background-color: #23305a;width: 300px;height: auto;margin: 120px auto 0 auto;color: #2ec0f6;}.tyg-div-form form {padding: 10px;}.tyg-div-form form input[type="text"] {width: 270px;height: 30px;margin: 25px 10px 0px 0px;}.tyg-div-form form button {cursor: pointer;width: 270px;height: 44px;margin-top: 25px;padding: 0;background: #2ec0f6;-moz-border-radius: 6px;-webkit-border-radius: 6px;border-radius: 6px;border: 1px solid #2ec0f6;-moz-box-shadow: 0 15px 30px 0 rgba(255, 255, 255, .25) inset,0 2px 7px 0 rgba(0, 0, 0, .2);-webkit-box-shadow: 0 15px 30px 0 rgba(255, 255, 255, .25) inset,0 2px 7px 0 rgba(0, 0, 0, .2);box-shadow: 0 15px 30px 0 rgba(255, 255, 255, .25) inset,0 2px 7px 0 rgba(0, 0, 0, .2);font-family: 'PT Sans', Helvetica, Arial, sans-serif;font-size: 14px;font-weight: 700;color: #fff;text-shadow: 0 1px 2px rgba(0, 0, 0, .1);-o-transition: all .2s;-moz-transition: all .2s;-webkit-transition: all .2s;-ms-transition: all .2s;}</style><body> <div class="tyg-div"><ul><li>讓</li><li><div style="margin-left:20px;">數</div></li><li><div style="margin-left:40px;">據</div></li><li><div style="margin-left:60px;">改</div></li><li><div style="margin-left:80px;">變</div></li><li><div style="margin-left:100px;">生</div></li><li><div style="margin-left:120px;">活</div></li></ul> </div> <div id="contPar" class="contPar"><div id="page1" style="z-index:1;"><div class="title0">SHIRO-DEMO</div><div class="title1">自強 民主 文明 和諧</div><div class="imgGroug"><ul><img alt="" class="img0 png" th:src="@{/img/page1_0.png}"><img alt="" class="img1 png" th:src="@{/img/page1_1.png}"><img alt="" class="img2 png" th:src="@{/img/page1_2.png}"></ul></div><img alt="" class="img3 png" th:src="@{/img/page1_3.jpg}"></div> </div> <div class="tyg-div-denglv"><div class="tyg-div-form"><form th:action="@{/user/login}" method="post"><h2>登錄</h2><p class="tyg-p">歡迎登錄</p><div style="margin:5px 0px;"><input type="text" name="username" placeholder="請輸入賬號..."/></div><div style="margin:5px 0px;"><input type="text" name="password" placeholder="請輸入密碼..."/></div><div style="margin:5px 0px;"><input type="text" name="code" style="width:150px;" placeholder="請輸入驗證碼..."/><img th:src="@{/user/getImage}" style="vertical-align:bottom;" alt="驗證碼"/></div><button type="submit">登<span style="width:20px;"></span>錄</button><a th:href="@{/user/registerview}">注冊</a></form></div> </div><script th:src="@{/js/jquery-1.8.0.min.js}"></script> <script th:src="@{/js/com.js}"></script> </body> </html> <!doctype html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head><meta charset="UTF-8"><meta name="viewport"content="width=device-width, mvcUser-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>用戶注冊</title> </head> <body><h1>用戶注冊</h1><form th:action="@{/user/register}" method="post">用戶名:<input type="text" name="username" > <br/>密碼 : <input type="text" name="password"> <br><input type="submit" value="立即注冊"></form></body> </html> <!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"> <head><meta charset="UTF-8"><title>首頁</title> </head> <body><h1>歡迎訪問主頁</h1><br><!--獲取身份信息--><span shiro:principal=""></span><br><!--認證處理--><span shiro:authenticated="">認證通過展示內容</span><br><span shiro:notAuthenticated="">沒有認證展示的內容</span><br><!--授權角色--><span shiro:hasRole="admin">admin角色用戶能看到此模塊</span><br><!--資源授權--><span shiro:hasPermission="product:*:01">具有product操作權限的用戶可看到</span><br></body> </html>4 注意事項
代碼板書是按照流程將主要代碼做了板書,其實注意事項在代碼的注解中也很詳細:
這里主要強調以下幾點:
1 盡量不要使用Lombok插件,因為這樣會增大代碼的耦合性,在實際開發中如果你使用了Lombok插件,別人使用你的代碼可能也需要安裝Lombok插件才能正常運行.具體原因可以谷歌或者百度(和jdk版本等都有很大關系)
2 如果實體使用了實現了序列化接口,那么我們在Ream中設置鹽的時候就必須自定義CustumByteSource并實現序列化接口;
3 我們在ShiroConfig.java中使用了MD5加密和散列來保障數據的安全,散列的次數越多,安全性越高
5 代碼地址
https://gitee.com/mrkay0313/boot-shiro-template.git
6 說明
項目中還使用了sl4j 和logback來完成日志的記錄;具體可以百度:
這里其實不推薦使用這種方式,我推薦使用將日志記錄到數據庫中:
參看我以前文章可自行實現:
https://blog.csdn.net/weixin_44848900/article/details/109707239
總結
以上是生活随笔為你收集整理的SpringBoot整合Shiro搭建登录注册认证授权权限项目模板的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 核壳油溶性CaSe/CdS/ZnS量子点
- 下一篇: SSM项目实例——简易版图书管理系统