1.什麼是shrio?
官方定義
意思是:
Apache Shrio是一個強大且使用簡單的Java加密框架,可以進行驗證,授權,加密和會話管理。Shrio有簡單且容易理解的API,你可以快速且容易的保護任何應用程序——小的移動應用程序和更大的企業級應用和web網站(純屬個人翻譯,沒有用第三方翻譯)
官網地址:http://shiro.apache.org/
源碼地址:項目源碼
2.什麼是Authentication(驗證)
shrio官方文檔提到了它的四個用處,先用一下Authentication做一個註冊和登陸驗證
看一下什麼Authentication
3.理解清楚概念,SpringBoot集成shrio,直接上代碼
- 引入需要的shrio依賴
pom文件
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
註冊
-註冊需要注意的就是對用戶密碼加密處理
controller
@RestController
@RequestMapping("user")
@Slf4j
public class UserController {
@Autowired
private UserService userService;
/**
* @return
* @author Ling
* @Param
* @Date 2020/2/28 11:18
* @Description 用戶註冊
*/
@PostMapping("/register")
public Result register(@RequestBody User user) {
Result result = new Result();
if (null == user) {
result.setResultCode(ResultCode.USER_USERINFO_NOT_BLANK);
}
result = userService.saveUser(user);
return result;
}
}
Service
@Service
public class UserServiceImpl implements UserService {
@Autowired(required = false)
private UserDao userDao;
@Override
public Result saveUser(User user) {
Result r = new Result();
//判斷用戶是否爲新用戶
String accout = user.getAccount();
User userInfo = userDao.findUserInfoByAccount(accout);
System.out.println("查詢結果爲" + userInfo);
if(null != userInfo){
r.setResultCode(ResultCode.USER_ALREADY_EXISTS);
}else{
//自定義加密類型對用戶密碼進行加密
PwdUtils.pwdEncryption(user);
//根據圖片命名,爲用戶隨機生成一個頭像
int index = new Random().nextInt(6)+1;
String userHeader = "static/header/user_"+index+".png";
user.setUserHeader(userHeader);
userDao.saveUser(user);
r.setResultCode(ResultCode.SUCCESS);
}
return r;
}
密碼加密,用的shrio自帶的加密方法SimpleHash
package com.ling.tools.login.utils;
import com.ling.tools.login.entity.User;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;
/**
* File Name: PwdUtils
* Date: 2020/2/28 15:09
* Author: liangling
* Description 用戶註冊密碼加密加鹽
*/
public class PwdUtils {
private static final String algorithmName = "md5";
private static final int hashIterations = 2;
/**
* 使用SimpleHash的構造器。四個參數
* algorithmName:要加密的算法名稱
* source:要加密的元數據,比如密碼
* salt:要一起加密的數據
* hashIterations: 代表hash迭代的次數,我設置的2,相當於md5(md5(""))
*/
public static void pwdEncryption(User user){
String salt = new SecureRandomNumberGenerator().nextBytes().toHex();//生成鹽
user.setSalt(salt);
String pwd = new SimpleHash(algorithmName,user.getPassword(), ByteSource.Util.bytes(user.getSalt()),hashIterations).toString();//生成密文
user.setPassword(pwd);
}
}
看了一眼源碼才知道的這四個參數
登陸驗證
1.自定義一個用戶信息驗證類
@Component
public class MyRealm extends AuthorizingRealm {
@Autowired
private UserDao userDao;
/**
* @return
* @author
* @Param
* @Date 2020/2/6 16:57
* @Des 驗證用戶名和密碼
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//自定義shrio判斷用戶名和密碼邏輯
//1.判斷用戶是否存在
String username = token.getPrincipal() + "";
User user = userDao.findUserInfoByAccount(username);
if (null == user) {
throw new UnknownAccountException("用戶不存在");
}
//校驗用戶名密碼
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user.getAccount(),
user.getPassword(),
ByteSource.Util.bytes(user.getSalt()),
getName());
return authenticationInfo;
}
編寫shrio配置文件,自定義密碼匹配憑證(自定義的密碼憑證必須和加密時候的算法名和次數一致,否則驗證不通過報錯),實例化安全管理器等
package com.ling.tools.login.config;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
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.springframework.aop.framework.DefaultAdvisorChainFactory;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* File Name: ShrioConfig
* Date: 2020/2/6 15:08
* Author: liangling
* Description
*/
@Configuration
public class ShrioConfig {
/**
* 安全管理器
*/
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(myRealm());
return defaultWebSecurityManager;
}
/**
* 憑證匹配器
* (由於我們的密碼校驗交給Shiro的SimpleAuthenticationInfo進行處理了
* 所以我們需要修改下doGetAuthenticationInfo中的代碼;
* )
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:這裏使用MD5算法;
hashedCredentialsMatcher.setHashIterations(2);//散列的次數,比如散列兩次,相當於 md5(md5(""));
return hashedCredentialsMatcher;
}
@Bean
public MyRealm myRealm(){
MyRealm myRealm = new MyRealm();
myRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myRealm;
}
/**
* 開啓shiro aop註解支持.
* 使用代理方式;所以需要開啓代碼支持;
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
controller
package com.ling.tools.login.controller;
import com.ling.tools.login.entity.User;
import com.ling.tools.login.service.UserService;
import com.ling.tools.login.utils.Result;
import com.ling.tools.login.utils.ResultCode;
import jdk.nashorn.internal.parser.JSONParser;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.LockedAccountException;
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.web.bind.annotation.*;
@RestController
@RequestMapping("user")
@Slf4j
public class UserController {
@Autowired
private UserService userService;
/**
* @return
* @author Ling
* @Param
* @Date 2020/2/28 11:18
* @Description 用戶註冊
*/
@PostMapping("/register")
public Result register(@RequestBody User user) {
Result result = new Result();
if (null == user) {
result.setResultCode(ResultCode.USER_USERINFO_NOT_BLANK);
}
result = userService.saveUser(user);
return result;
}
@PostMapping("/login")
public Result login(@RequestBody User user) {
Result result = new Result();
if(null == user){
result.setResultCode(ResultCode.USER_USERINFO_NOT_BLANK);
}
executeLogin(user,result);
return result;
}
private void executeLogin(User user,Result r) {
//獲取一個subject對象用來調用login方法進行驗證
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(user.getAccount(),user.getPassword());
try {
subject.login(token);
r.setResultCode(ResultCode.SUCCESS);
}catch (UnknownAccountException e){
r.setResultCode(ResultCode.USER_DOSENOT_EXISTS);
}catch (Exception e){
r.setResultCode(ResultCode.FAIL);
}
}
}
整個驗證過程的入口就是從subject.login(token)開始的,源碼經過幾層調用最後會調用自定義的MyRealm類進行驗證
代碼沒有放完整,源碼地址:源碼點這個