1.依賴
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
2.創建測試數據
1.創建數據庫用戶數據
2.創建UserMapper 獲取用戶數據
@Mapper
public interface UserMapper {
List<Userinfo> findAll();
}
UserMapper.xml
<?xml version="1.0" encoding ="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper3.0//EN "
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo2.mapper.UserMapper">
<select id="findAll" resultType="com.example.demo2.beans.Userinfo">
select * from userinfo;
</select>
</mapper>
3.創建ShiroConfig,添加相關設置
這裏面比如是否需要加密密碼(認證傳入Shiro的是明文,但是數據庫保存的可能是加密過的,所以需要相關的加密配置,Shiro根據這個配置對認證時傳入的密碼進行加密,然後進行匹配)
package com.example.demo2.config;
import com.example.demo2.app.UserRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
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 java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//md5加密1次
hashedCredentialsMatcher.setHashAlgorithmName("md5");
hashedCredentialsMatcher.setHashIterations(1);
hashedCredentialsMatcher.setHashSalted(true);
return hashedCredentialsMatcher;
}
//自定義realm
@Bean
public UserRealm userRealm() {
UserRealm userRealm = new UserRealm();
userRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return userRealm;
}
/**
* 安全管理器
* 注:使用shiro-spring-boot-starter 1.4時,返回類型是SecurityManager會報錯,直接引用shiro-spring則不報錯
*
* @return
*/
@Bean
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm());
return securityManager;
}
/**
* 設置過濾規則
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager manager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSecurityManager(manager);
shiroFilterFactoryBean.setSuccessUrl("/");
shiroFilterFactoryBean.setUnauthorizedUrl("/unauth");
//注意此處使用的是LinkedHashMap,是有順序的,shiro會按從上到下的順序匹配驗證,匹配了就不再繼續驗證
//所以上面的url要苛刻,寬鬆的url要放在下面,尤其是"/**"要放到最下面,如果放前面的話其後的驗證規則就沒作用了。
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/captcha.jpg", "anon");
filterChainDefinitionMap.put("/favicon.ico", "anon");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
}
4.創建UserRealm,用於身份權限劃分與認證匹配
package com.example.demo2.app;
import com.example.demo2.beans.Userinfo;
import com.example.demo2.mapper.UserMapper;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.DigestUtils;
import java.util.ArrayList;
import java.util.List;
public class UserRealm extends AuthorizingRealm {
@Autowired
UserMapper userMapper;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//添加授權認證用戶,一般是添加用戶信息
//一般在這裏進行用戶權限區分,把那些有權限的用戶加入進去
info.addStringPermissions(getUserNames());
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//獲取認證信息
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String userName = token.getUsername();
//一般來說,獲取到要驗證的用戶id,然後我們獲取數據庫的密碼,然後把賬號密碼傳入進去就行了
//驗證是有Shrio自動幫我們匹配
Userinfo userinfo = getUserByUserId(userName);
if(userinfo!=null){
//獲取MD5加密後的密碼,一般來說這個應該是存在數據庫的,不是明文密碼
//爲了測試方便,生成MD5密碼傳入,在ShrioConfig裏設置MD5加密一次,看上面ShiroConfig
//到時候驗證時傳入明文密碼,Shrio自動幫我們加密後再匹配
//就是說這個密碼是數據庫保存的密碼,一般來說,保存是啥就傳入啥
String pswInSecCode = getMD5(userinfo.getPassword());
return new SimpleAuthenticationInfo(userinfo.getUserName(), pswInSecCode, getName());
}
//找不到,說明用戶不存在,返回空
return null;
}
public static String getMD5(String str) {
String base = str+"";
String md5 = DigestUtils.md5DigestAsHex(base.getBytes());
return md5;
}
private List<String> getUserNames(){
List<Userinfo> userinfos = userMapper.findAll();
List<String> userNames = new ArrayList<>();
if (userinfos != null && userinfos.size() > 0) {
for (Userinfo userinfo : userinfos) {
userNames.add(userinfo.getUserName());
}
}
return userNames;
}
private Userinfo getUserByUserId(String userId){
List<Userinfo> userinfos = userMapper.findAll();
if (userinfos != null && userinfos.size() > 0) {
for (Userinfo userinfo : userinfos) {
if(userinfo.getUserName().equals(userId)){
return userinfo;
}
}
}
return null;
}
}
5.添加LoginController,用於登錄請求
package com.example.demo2.controller;
import com.example.demo2.config.ShiroConfig;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.DisabledAccountException;
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.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class LoginController {
/**
* get請求,登錄頁面跳轉
*
* @return
*/
@GetMapping("/login")
public String login() {
//如果已經認證通過,直接跳轉到首頁
if (SecurityUtils.getSubject().isAuthenticated()) {
return "redirect:/index";
}
return "login";
}
/**
* post表單提交,登錄
*
* @param username
* @param password
* @param model
* @return
*/
@PostMapping("/login")
public Object login(String username, String password, Model model) {
Subject user = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
System.out.println("username:" + username + ",psw:" + password);
try {
//shiro幫我們匹配密碼什麼的,我們只需要把東西傳給它,它會根據我們在UserRealm裏認證方法設置的來驗證
user.login(token);
return "redirect:index";
} catch (UnknownAccountException e) {
//賬號不存在和下面密碼錯誤一般都合併爲一個賬號或密碼錯誤,這樣可以增加暴力破解難度
model.addAttribute("message", "賬號不存在!");
} catch (DisabledAccountException e) {
model.addAttribute("message", "賬號未啓用!");
} catch (IncorrectCredentialsException e) {
model.addAttribute("message", "密碼錯誤!");
} catch (Throwable e) {
model.addAttribute("message", "未知錯誤!");
}
return "login";
}
/**
* 退出
*
* @return
*/
@RequestMapping("/logout")
public String logout() {
SecurityUtils.getSubject().logout();
return "login";
}
}
6.添加IndexController用於登錄成功後跳轉請求
package com.example.demo2.controller;
import org.apache.shiro.SecurityUtils;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class IndexController {
/**
* 首頁,並將登錄用戶的全名返回前臺
* @param model
* @return
*/
@RequestMapping(value = {"/", "/index"})
public String index(Model model) {
String sysUser = (String) SecurityUtils.getSubject().getPrincipal();
model.addAttribute("userName", sysUser);
return "index";
}
}
7.添加jsp,用於前端測試
依賴:
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
添加jsp:
login.jsp用戶登錄:
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<title>登錄</title>
</head>
<body>
<h1>${message}</h1>
<form method="post" action="/login">
用戶名:<input name="username"><br>
密碼:<input name="password"><br>
<input type="submit" value="登錄">
</form>
</body>
</html>
index.jsp用於登錄成功:
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<title>首頁</title>
</head>
<body>
hello ${userName}
</body>
</html>
8.運行測試
輸入密碼錯誤提示:
輸入密碼成功後跳轉:
9.session管理
一般默認都支持session,按照上面的教程,沒有登錄或者session過期後訪問任何接口與任何頁面都會返回登錄界面。
我們可以自定義session配置,在上面的ShiroConfig類裏,添加下面:
/**
* 安全管理器
* 注:使用shiro-spring-boot-starter 1.4時,返回類型是SecurityManager會報錯,直接引用shiro-spring則不報錯
*
* @return
*/
@Bean
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm());
securityManager.setSessionManager(sessionManager());
return securityManager;
}
//使用默認的MemorySessionDAO
@Bean
public SessionDAO sessionDAO() {
return new MemorySessionDAO();
}
/**
* shiro session的管理
*/
@Bean
public DefaultWebSessionManager sessionManager() { //配置默認的sesssion管理器
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
//設置session過期爲60秒,默認30分鐘
sessionManager.setGlobalSessionTimeout(60 * 1000);
sessionManager.setSessionDAO(sessionDAO());
Collection<SessionListener> listeners = new ArrayList<>();
listeners.add(new BDSessionListener());
sessionManager.setSessionListeners(listeners);
return sessionManager;
}
//Session監聽
private class BDSessionListener implements SessionListener{
@Override
public void onStart(Session session) {
}
@Override
public void onStop(Session session) {
}
@Override
public void onExpiration(Session session) {
}
}
瀏覽器登錄成功後。60秒內,關閉頁面再進入,不需要重新登錄:
不操作60秒後,需要重新登錄。
取消對接口的限制
Shiro可以編譯過濾攔截規則,具體在ShiroConfig裏面。
/**
* 設置過濾規則
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager manager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSecurityManager(manager);
shiroFilterFactoryBean.setSuccessUrl("/");
shiroFilterFactoryBean.setUnauthorizedUrl("/unauth");
//注意此處使用的是LinkedHashMap,是有順序的,shiro會按從上到下的順序匹配驗證,匹配了就不再繼續驗證
//所以上面的url要苛刻,寬鬆的url要放在下面,尤其是"/**"要放到最下面,如果放前面的話其後的驗證規則就沒作用了。
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/captcha.jpg", "anon");
filterChainDefinitionMap.put("/favicon.ico", "anon");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
按照這個規則,所有請求接口都會攔截,就是任何請求,沒有登錄過都會返回登錄頁面。
可以按照下面的修改,只攔截指定頁面:
/**
* 設置過濾規則
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager manager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSecurityManager(manager);
shiroFilterFactoryBean.setSuccessUrl("/");
shiroFilterFactoryBean.setUnauthorizedUrl("/unauth");
//注意此處使用的是LinkedHashMap,是有順序的,shiro會按從上到下的順序匹配驗證,匹配了就不再繼續驗證
//所以上面的url要苛刻,寬鬆的url要放在下面,尤其是"/**"要放到最下面,如果放前面的話其後的驗證規則就沒作用了。
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/captcha.jpg", "anon");
filterChainDefinitionMap.put("/favicon.ico", "anon");
//authc是需要認證的,anon是不需要攔截認證
// filterChainDefinitionMap.put("/**", "authc");
//我們這裏設置index才需要攔截認證,其他接口都不攔截即可
filterChainDefinitionMap.put("/index", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
具體一些更多相關,可以參下這篇文章https://www.cnblogs.com/qsymg/p/9836122.html
相關參考:
本文參考借鑑於https://blog.csdn.net/gnail_oug/article/details/80662553,感謝。