關于shiro的概念和知識本篇不做詳細介紹,但是shiro的概念還是需要做做功課的要不無法理解它的運作原理就無法理解使用shiro;
本篇主要講解如何使用shiro實現登錄認證,下篇講解使用shiro實現權限控制
要實現shiro和springboot的整合需要以下幾大步驟:
- 生成用戶表
- 引入shiro依賴
- 添加shiro配置文件
- 添加自定義的realm
- 登錄操作觸發驗證
- 細節處理
下面我們一步步的詳細介紹:
一、生成用戶表
CREATE TABLE `sys_user` (`user_id` bigint(20) NOT NULL AUTO_INCREMENT,`username` varchar(50) NOT NULL COMMENT '用戶名',`password` varchar(100) DEFAULT NULL COMMENT '密碼',`salt` varchar(20) DEFAULT NULL COMMENT '鹽',`email` varchar(100) DEFAULT NULL COMMENT '郵箱',`mobile` varchar(100) DEFAULT NULL COMMENT '手機號',`status` tinyint(4) DEFAULT NULL COMMENT '狀態 0:禁用 1:正常',`dept_id` bigint(20) DEFAULT NULL COMMENT '部門ID',`create_time` datetime DEFAULT NULL COMMENT '創建時間',PRIMARY KEY (`user_id`),UNIQUE KEY `username` (`username`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='系統用戶';
二、引入shiro依賴
<!-- Apache shiro依賴 只需要引入本依賴 shiro-spring 會自動引入shiro-web和shiro-core依賴--> <dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.3.2</version> </dependency>
三、添加shiro的配置文件(本篇使用的是@Configuration注解java類的方式,也可以使用xml的方式)
package com.chuhouqi.demo.shiro;import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;@Configuration
public class ShiroConfig {@Bean("sessionManager")public SessionManager sessionManager(){DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();sessionManager.setSessionValidationSchedulerEnabled(true);sessionManager.setSessionIdUrlRewritingEnabled(false);//sessionManager.setSessionIdCookieEnabled(false);return sessionManager;}@Bean("securityManager")public SecurityManager securityManager(UserRealm userRealm, SessionManager sessionManager) {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();securityManager.setRealm(userRealm);securityManager.setSessionManager(sessionManager);return securityManager;}@Bean("shiroFilter")public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();shiroFilter.setSecurityManager(securityManager);shiroFilter.setLoginUrl("/login");shiroFilter.setSuccessUrl("/index");shiroFilter.setUnauthorizedUrl("/403");Map<String, String> filterMap = new LinkedHashMap<>();filterMap.put("/druid/**", "anon");filterMap.put("/api/**", "anon");filterMap.put("/login", "anon");filterMap.put("/registe", "anon");filterMap.put("/registe.html", "anon");filterMap.put("/**/*.css", "anon");filterMap.put("/**/*.js", "anon");// filterMap.put("/login.html", "anon");filterMap.put("/fonts/**", "anon");filterMap.put("/plugins/**", "anon");filterMap.put("/swagger/**", "anon");filterMap.put("/favicon.ico", "anon");filterMap.put("/captcha.jpg", "anon");filterMap.put("/", "anon");filterMap.put("/**", "authc");shiroFilter.setFilterChainDefinitionMap(filterMap);return shiroFilter;}@Bean("lifecycleBeanPostProcessor")public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {return new LifecycleBeanPostProcessor();}@Beanpublic DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();proxyCreator.setProxyTargetClass(true);return proxyCreator;}@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();advisor.setSecurityManager(securityManager);return advisor;}}
四、添加自定義的realm(實現認證和授權)
package com.chuhouqi.demo.shiro;import com.chuhouqi.demo.common.utils.ShiroUtil; import com.chuhouqi.demo.entity.User; import com.chuhouqi.demo.service.IUserService; import org.apache.shiro.authc.*; import org.apache.shiro.authc.credential.CredentialsMatcher; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;@Component public class UserRealm extends AuthorizingRealm {@Autowiredprivate IUserService userService;@Override/*** 權限授權*/protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {return null;}@Override/*** 登錄認證*/protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {//獲取用戶輸入的用戶名String username = (String) token.getPrincipal();//根據用戶名查詢用戶信息User user = userService.getUser(username);// 賬號不存在if (user == null) {throw new UnknownAccountException("賬號不存在");}// 賬號鎖定if (user.getStatus() == 0) {throw new LockedAccountException("賬號已被鎖定,請聯系管理員");}SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(),ByteSource.Util.bytes(user.getSalt()),getName());return info;}/*** 設置密碼比較器為HashedCredentialsMatcher* @param credentialsMatcher*/@Overridepublic void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {HashedCredentialsMatcher shaCredentialsMatcher = new HashedCredentialsMatcher();shaCredentialsMatcher.setHashAlgorithmName(ShiroUtil.hashAlgorithmName);shaCredentialsMatcher.setHashIterations(ShiroUtil.hashIterations);super.setCredentialsMatcher(shaCredentialsMatcher);}}
五、登錄操作觸發驗證
package com.chuhouqi.demo.controller;import com.chuhouqi.demo.common.utils.ShiroUtil; import com.chuhouqi.demo.entity.User; import com.chuhouqi.demo.service.IUserService; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping;@Controller public class LoginController {private static Logger logger = LoggerFactory.getLogger(LoginController.class);@Autowiredprivate IUserService userService;@GetMapping("/login")String login() {return "login";}@RequestMapping("/login")public String login(String username, String password, Model model){try {Subject subject = SecurityUtils.getSubject();UsernamePasswordToken token = new UsernamePasswordToken(username, password);subject.login(token);}catch (UnknownAccountException e) {logger.error(e.getMessage());model.addAttribute("msg",e.getMessage());return "login";}catch (IncorrectCredentialsException e) {logger.error(e.getMessage());model.addAttribute("msg","賬號或密碼不正確");return "login";}catch (LockedAccountException e) {logger.error(e.getMessage());model.addAttribute("msg","賬號已被鎖定,請聯系管理員");return "login";}catch (AuthenticationException e) {logger.error(e.getMessage());model.addAttribute("msg","賬戶驗證失敗");return "login";}return "index";}@RequestMapping("/registe")public String registe(User user){userService.saveUser(user);return "ok";}@RequestMapping("/logout")public String logout(){ShiroUtil.logout();return "redirect:/login";}}
驗證:
啟動項目,然后隨便請求一個路徑都會被shiro配置的filter攔截器進行攔截,如果請求的路徑需要權限認證就會進入shiro的認證管理中,如果當前用戶沒有登錄就會調整到登錄頁面;
六、細節處理:
上面的介紹只是給出了一個大概的流程,其中有很多細節還是要特比注意的要不會導致認證失敗,下面我們看一下有哪些細節需要處理
1、用戶密碼加密處理
在數據庫中存儲的用戶密碼不應該是123456這樣的密碼明文,被不法分子看到是很危險的,所以數據庫中的密碼應該是對密碼進行加密后的密文,而且還要求這個加密算法是不可逆的,即由加密后的字符串不能反推回來原來的密碼,如果能反推回來那這個加密是沒有意義的。
現在常用的加密算法有:?MD5,SHA1
而且shiro提供了SimpleHash這個加密工具來實現密碼加密:
public final static String hashAlgorithmName = "SHA-256";//加密算法
public final static int hashIterations = 16;//hash加密次數
public static String encrypt(String pwd,String salt){
String newPassword = new SimpleHash(hashAlgorithmName,pwd,salt,hashIterations).toHex();
return newPassword;
}
如果兩個人的密碼一樣,即存在數據表里中的兩個加密后的字符串一樣,然而我們希望即使兩個人的密碼一樣,加密后的兩個字符串也不一樣。即需要用到MD5鹽值加密。
鹽值需要唯一: 一般使用隨機字符串或 user id
這里提供一個工具:
String salt = RandomStringUtils.randomAlphanumeric(20);//使用隨機數函數生成salt
2、配置shiro的密碼比較器
上面我們用加密算法實現了密碼的明文加密,現在數據庫中存儲的是密碼密文,用戶登錄時使用的密碼原文,如果不告訴shiro我們的密碼加密算法邏輯,shiro是使用默認的比較器
進行的簡單的密碼比較(即使用數據庫中的密碼密文和用戶登錄時輸入的密碼明文進行比較),顯而易見這樣比較是不會成功的所以我們要告訴shiro 我們是使用的加密算法,
實現過程很簡單:在我們自定義的UserRealm中添加如下配置
/*** 設置密碼比較器為HashedCredentialsMatcher* @param credentialsMatcher*/@Overridepublic void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {HashedCredentialsMatcher shaCredentialsMatcher = new HashedCredentialsMatcher();shaCredentialsMatcher.setHashAlgorithmName(ShiroUtil.hashAlgorithmName);//這里就是我們進行密碼加密的算法shaCredentialsMatcher.setHashIterations(ShiroUtil.hashIterations);//加密循環次數super.setCredentialsMatcher(shaCredentialsMatcher);}
?