springboot整合shiro實現多realm不同資料表登陸
阿新 • • 發佈:2018-12-29
shrio是一個很好的登陸以及許可權管理框架,但是mo預設是單realm單資料表,如果業務中使用者分佈在不同的資料表,單realm就很難實現登陸以及許可權管理的功能,這篇部落格就簡單的介紹一個家長 學生 老師的shiro的多realm登陸驗證,使用springboot,mybatis mysql等相關技術,部落格底部附上原始碼,有興趣的可以去下載
1.專案pom依賴
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.0.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </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> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
2.使用者資料表
CREATE DATABASE /*!32312 IF NOT EXISTS*/`springboot_shiro_mulit_relam` /*!40100 DEFAULT CHARACTER SET utf8 */; USE `springboot_shiro_mulit_relam`; /*Table structure for table `parent` */ DROP TABLE IF EXISTS `parent`; CREATE TABLE `parent` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(20) DEFAULT NULL, `account` varchar(20) DEFAULT NULL, `password` varchar(20) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; /*Data for the table `parent` */ insert into `parent`(`id`,`name`,`account`,`password`) values (1,'張家長','123','123'); /*Table structure for table `student` */ DROP TABLE IF EXISTS `student`; CREATE TABLE `student` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(20) DEFAULT NULL, `account` varchar(20) DEFAULT NULL, `password` varchar(20) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; /*Data for the table `student` */ insert into `student`(`id`,`name`,`account`,`password`) values (1,'王同學','456','456'); /*Table structure for table `teacher` */ DROP TABLE IF EXISTS `teacher`; CREATE TABLE `teacher` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(20) DEFAULT NULL, `account` varchar(20) DEFAULT NULL, `password` varchar(20) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; /*Data for the table `teacher` */ insert into `teacher`(`id`,`name`,`account`,`password`) values (1,'劉老師','789','789');
3.shiro 配置
實現多realm的關鍵在於繼承shiro預設的UsernamePasswordToken,新增一個欄位用於標識不同的realm,使用者登入的shi時候帶上標識的欄位,shiro則根據欄位去不同的realm去驗證登陸,授權等
3.1 繼承UsernamePasswordToken新增loginType屬性
public class UserToken extends UsernamePasswordToken { private String loginType; public UserToken() {} public UserToken(final String username, final String password, final String loginType) { super(username, password); this.loginType = loginType; } public String getLoginType() { return loginType; } public void setLoginType(String loginType) { this.loginType = loginType; } }
3.2 登陸時使用繼承後的UserToken,新增loginType屬性
@RequestMapping("/studentLogin")
public Student studentLogin(@RequestParam(value = "account") String account,
@RequestParam(value = "password") String password,
HttpServletRequest request,
HttpServletResponse response) {
System.out.println("\n學生登陸");
Student student = null;
//設定永不過期
SecurityUtils.getSubject().getSession().setTimeout(-1000L);
Subject subject = SecurityUtils.getSubject();
try {
// 呼叫安全認證框架的登入方法
subject.login(new UserToken(account, password, "Student"));
student = studentService.getStudentByAccount(account);
} catch (AuthenticationException ex) {
System.out.println("登陸失敗: " + ex.getMessage());
}
return student;
}
3.3 獲得AuthenticationToken,判斷是單realm還是多realm,分別去不同的方法驗證
public class UserModularRealmAuthenticator extends ModularRealmAuthenticator {
@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken)
throws AuthenticationException {
System.out.println("UserModularRealmAuthenticator:method doAuthenticate() execute ");
// 判斷getRealms()是否返回為空
assertRealmsConfigured();
// 強制轉換回自定義的CustomizedToken
UserToken userToken = (UserToken) authenticationToken;
// 登入型別
String loginType = userToken.getLoginType();
// 所有Realm
Collection<Realm> realms = getRealms();
// 登入型別對應的所有Realm
List<Realm> typeRealms = new ArrayList<>();
for (Realm realm : realms) {
if (realm.getName().contains(loginType)) {
typeRealms.add(realm);
}
}
// 判斷是單Realm還是多Realm
if (typeRealms.size() == 1){
System.out.println("doSingleRealmAuthentication() execute ");
return doSingleRealmAuthentication(typeRealms.get(0), userToken);
}
else{
System.out.println("doMultiRealmAuthentication() execute ");
return doMultiRealmAuthentication(typeRealms, userToken);
}
}
}
3.4 配置明文密碼登陸,方便測試
public class CustomCredentialsMatcher extends SimpleCredentialsMatcher {
@Override
public boolean doCredentialsMatch(AuthenticationToken authcToken, AuthenticationInfo info) {
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
// Object tokenCredentials = encrypt(String.valueOf(token.getPassword()));
//明文密碼
Object tokenCredentials = String.valueOf(token.getPassword());
Object accountCredentials = getCredentials(info);
//將密碼加密與系統加密後的密碼校驗,內容一致就返回true,不一致就返回false
return equals(tokenCredentials, accountCredentials);
}
/**
* @author Adam
* @description 將傳進來密碼加密方法
* @date 11:26 2018/9/2
* @param [data]
* @return java.lang.String
*/
private String encrypt(String data) {
//這裡可以選擇自己的密碼驗證方式 比如 md5或者sha256等
String sha384Hex = new Sha384Hash(data).toBase64();
return sha384Hex;
}
}
3.5 shiroconfig 配置 (將三個使用者realm 注入)
@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("/image/**", "anon");
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/html/**", "anon");
//測試開放的介面
filterChainDefinitionMap.put("/login/**", "anon");
filterChainDefinitionMap.put("/favicon.ico", "anon");
//配置退出 過濾器,其中的具體的退出程式碼Shiro已經替我們實現了
filterChainDefinitionMap.put("/logout", "logout");
//<!-- 過濾鏈定義,從上向下順序執行,一般將/**放在最為下邊 -->:這是一個坑呢,一不小心程式碼就不好使了;
//<!-- authc:所有url都必須認證通過才可以訪問; anon:所有url都都可以匿名訪問-->
// filterChainDefinitionMap.put("/**", "authc");
filterChainDefinitionMap.put("/**", "anon");
// 如果不設定預設會自動尋找Web工程根目錄下的"/login.jsp"頁面
shiroFilterFactoryBean.setLoginUrl("/login/unauth");
//未授權介面;
shiroFilterFactoryBean.setUnauthorizedUrl("/login/unauth");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 憑證匹配器
* (由於我們的密碼校驗交給Shiro的SimpleAuthenticationInfo進行處理了)
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//雜湊演算法:這裡使用MD5演算法;
hashedCredentialsMatcher.setHashAlgorithmName("md5");
//雜湊的次數,比如雜湊兩次,相當於 md5(md5(""));
hashedCredentialsMatcher.setHashIterations(2);
return hashedCredentialsMatcher;
}
@Bean(name = "parentRealm")
public ParentRealm parentRealm(){
ParentRealm parentRealm = new ParentRealm();
parentRealm.setCredentialsMatcher(new CustomCredentialsMatcher());
return parentRealm;
}
@Bean(name = "teacherRealm")
public TeacherRealm teacherRealm(){
TeacherRealm teacherRealm = new TeacherRealm();
teacherRealm.setCredentialsMatcher(new CustomCredentialsMatcher());
return teacherRealm;
}
@Bean(name = "studentRealm")
public StudentRealm studentRealm(){
StudentRealm studentRealm = new StudentRealm();
studentRealm.setCredentialsMatcher(new CustomCredentialsMatcher());
return studentRealm;
}
@Bean(name = "SecurityManager")
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//設定realm.
securityManager.setAuthenticator(modularRealmAuthenticator());
List<Realm> realms = new ArrayList<>();
//新增多個Realm
realms.add(parentRealm());
realms.add(teacherRealm());
realms.add(studentRealm());
securityManager.setRealms(realms);
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);
r.setDefaultErrorView("error");
r.setExceptionAttribute("ex");
return r;
}
/**
* 系統自帶的Realm管理,主要針對多realm
* */
@Bean
public ModularRealmAuthenticator modularRealmAuthenticator(){
//自己重寫的ModularRealmAuthenticator
UserModularRealmAuthenticator modularRealmAuthenticator = new UserModularRealmAuthenticator();
modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
return modularRealmAuthenticator;
}
3.6 realm 注入service層,去資料庫中查詢資料,驗證密碼
public class ParentRealm extends AuthorizingRealm {
@Autowired
private ParentService parentService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("許可權配置-->MyShiroRealm.doGetAuthorizationInfo()");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
return authorizationInfo;
}
/**
* @Author Adam
* @Description 主要是用來進行身份認證的,也就是說驗證使用者輸入的賬號和密碼是否正確
* @Date 14:06 2018/9/28
* @Param [token]
* @return org.apache.shiro.authc.AuthenticationInfo
**/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
System.out.println("MyShiroRealm.doGetAuthenticationInfo()");
//獲取使用者的輸入的賬號.
String account = (String)token.getPrincipal();
Object credentials = token.getCredentials();
System.out.println("credentials="+credentials);
//通過username從資料庫中查詢物件
//實際專案中,這裡可以根據實際情況做快取,如果不做,Shiro自己也是有時間間隔機制,2分鐘內不會重複執行該方法
Parent parent = parentService.getParentByAccount(account);
System.out.println("----->>parent="+parent);
if(parent == null){
return null;
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
parent,
parent.getPassword(),
getName()
);
return authenticationInfo;
}
}
public class StudentRealm extends AuthorizingRealm {
@Autowired
private StudentService studentService;
/**
* @Author Adam
* @Description 許可權配置
* @Date 14:29 2018/9/28
* @Param [principals]
* @return org.apache.shiro.authz.AuthorizationInfo
**/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("許可權配置-->MyShiroRealm.doGetAuthorizationInfo()");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
return authorizationInfo;
}
/**
* @Author Adam
* @Description 主要是用來進行身份認證的,也就是說驗證使用者輸入的賬號和密碼是否正確。
* @Date 14:24 2018/9/28
* @Param [token]
* @return org.apache.shiro.authc.AuthenticationInfo
**/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
System.out.println("MyShiroRealm.doGetAuthenticationInfo()");
//獲取使用者的輸入的賬號.
String account = (String)token.getPrincipal();
Object credentials = token.getCredentials();
System.out.println("credentials="+credentials);
//通過username從資料庫中查詢物件
Student student = studentService.getStudentByAccount(account);
System.out.println("----->>student="+student);
if(student == null){
return null;
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
student,
student.getPassword(),
getName()
);
return authenticationInfo;
}
}
public class TeacherRealm extends AuthorizingRealm {
@Autowired
private TeacherService teacherService;
/**
* @Author Adam
* @Description 許可權配置
* @Date 14:09 2018/9/28
* @Param [principals]
* @return org.apache.shiro.authz.AuthorizationInfo
**/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("許可權配置-->MyShiroRealm.doGetAuthorizationInfo()");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
return authorizationInfo;
}
/**
* @Author Adam
* @Description 主要是用來進行身份認證的,也就是說驗證使用者輸入的賬號和密碼是否正確
* @Date 14:08 2018/9/28
* @Param [token]
* @return org.apache.shiro.authc.AuthenticationInfo
**/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
System.out.println("MyShiroRealm.doGetAuthenticationInfo()");
//獲取使用者的輸入的賬號.
String account = (String)token.getPrincipal();
Object credentials = token.getCredentials();
System.out.println("credentials="+credentials);
//通過username從資料庫中查詢物件
//實際專案中,這裡可以根據實際情況做快取,如果不做,Shiro自己也是有時間間隔機制,2分鐘內不會重複執行該方法
Teacher teacher = teacherService.getTeacherByAccount(account);
System.out.println("----->>teacher="+teacher);
if(teacher == null){
return null;
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
teacher,
teacher.getPassword(),
getName()
);
return authenticationInfo;
}
}