寫在前面:
關于shiro介紹以及shiro整合spring,我在另一篇文章中已詳細介紹彬碱,此處不作說明,請參考spring整合shiro奥洼。點我下載源碼巷疼。
歡迎大家關注我的公眾號 javawebkf,目前正在慢慢地將簡書文章搬到公眾號灵奖,以后簡書和公眾號文章將同步更新嚼沿,且簡書上的付費文章在公眾號上將免費。
開發(fā)環(huán)境:
1瓷患、mysql - 5.7.21
2骡尽、navicat(mysql客戶端管理工具)
3、idea 2017
4尉尾、jdk9
5爆阶、tomcat 8.5
6、springboot
7沙咏、mybatis 3
8辨图、shiro
9、maven
項目開始:
一肢藐、數(shù)據(jù)庫設計:
注:數(shù)據(jù)庫三張表和spring整合shiro中的一模一樣故河,在那邊已經(jīng)詳細說明,這里直接大家看下三張表的ER圖吆豹。
二鱼的、添加依賴,配置mybatis
1痘煤、用idea新建Spring Initializr項目凑阶,項目結(jié)構如下:
2、添加依賴:
<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-tomcat</artifactId>
<scope>provided</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-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.20</version>
</dependency>
<!--常用的工具包-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<!--spring的上下文工具包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<!--對jsp的處理-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
</dependencies>
3衷快、application.properties
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql:///#
spring.datasource.username=#
spring.datasource.password=#
## mybatis ##
mybatis.mapper-locations=mappers/*.xml
mybatis.type-aliases-package=com.zhu.shiro.entity
## 視圖解析器 ##
spring.mvc.view.prefix=/pages/
spring.mvc.view.suffix=.jsp
三宙橱、項目設計
注:spring整合shiro中是只有User實體類,在UserDao中定義了三個方法蘸拔,通過表的關鍵關系查詢Role和Permission;這里將采用另一種方式师郑,三個實體類,設置實體類的關聯(lián)關系调窍。
1宝冕、entity層
User.java
public class User {
private Integer uid;
private String username;
private String password;
private Set<Role> roles = new HashSet<>();
}
Role.java
public class Role {
private Integer rid;
private String name;
private Set<Permission> permissions = new HashSet<>();
}
Permission.java
public class Permission {
private Integer pid;
private String name;
}
2、dao層
UserDao.java
public interface UserDao {
User findByUsername(String username);
}
UserDao.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhu.shiro.dao.UserDao">
<resultMap id="userMap" type="com.zhu.shiro.entity.User">
<id property="uid" column="uid"/>
<result property="username" column="user_name"/>
<result property="password" column="pass_word"/>
<collection property="roles" ofType="com.zhu.shiro.entity.Role">
<id property="rid" column="rid"/>
<result property="name" column="role_name"/>
<collection property="permissions" ofType="com.zhu.shiro.entity.Permission">
<id property="pid" column="pid"/>
<result property="name" column="permission_name"/>
</collection>
</collection>
</resultMap>
<select id="findByUsername" parameterType="string" resultMap="userMap">
SELECT *
FROM tb_user u,tb_role r,tb_permission p
WHERE u.rid=r.rid
AND p.rid=r.rid
AND u.user_name=#{username}
</select>
</mapper>
3邓萨、service層
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public User findByUsername(String username) {
return userDao.findByUsername(username);
}
}
4地梨、junit測試
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceImplTest {
@Autowired
private UserService userService;
@Test
public void findByUsername() {
User u = userService.findByUsername("tom");
Set<Role> roleSet = u.getRoles();
for (Role role : roleSet){
Set<Permission> permissionSet = role.getPermissions();
for (Permission permission : permissionSet){
System.out.println(permission.getName());
}
System.out.println(role.getName());
}
}
}
運行結(jié)果:
數(shù)據(jù)庫中tom是admin角色菊卷,有增刪改查權限,符合預期湿刽,測試通過的烁。
5、controller層
TestController.java
@Controller
public class TestController {
//用戶登錄
@RequestMapping("/loginUser")
public String loginUser(@RequestParam("username") String username,
@RequestParam("password") String password,
HttpSession session) {
//把前端輸入的username和password封裝為token
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
session.setAttribute("user", subject.getPrincipal());
return "index";
} catch (Exception e) {
return "login";
}
}
//退出登錄
@RequestMapping("/logout")
public String logout() {
Subject subject = SecurityUtils.getSubject();
if (subject != null) {
subject.logout();
}
return "login";
}
//訪問login時跳到login.jsp
@RequestMapping("/login")
public String login() {
return "login";
}
//admin角色才能訪問
@RequestMapping("/admin")
@ResponseBody
public String admin() {
return "admin success";
}
//有delete權限才能訪問
@RequestMapping("/edit")
@ResponseBody
public String edit() {
return "edit success";
}
@RequestMapping("/test")
@ResponseBody
@RequiresRoles("guest")
public String test(){
return "test success";
}
}
說明:這里用戶登錄方法用到了shiro诈闺,但是這里還沒配置shiro渴庆,所以暫時不能使用,先搭起整個骨架雅镊,然后再加入shiro襟雷。
6、jsp頁面
login.jsp
(登錄頁面)
%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Login</title>
</head>
<body>
<h1>歡迎登錄仁烹!</h1>
<form action="/loginUser" method="post">
<input type="text" name="username"><br>
<input type="password" name="password"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
index.jsp
(登錄成功跳轉(zhuǎn)的頁面)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>歡迎登錄耸弄,${user.username}</h1>
</body>
</html>
unauthorized.jsp
(無權訪問跳轉(zhuǎn)的頁面)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>unauthorized</title>
</head>
<body>
unauthorized!
</body>
</html>
現(xiàn)在說一下要求:
admin路由要求只有具有admin角色的用戶才能訪問,edit路由需要有delete權限的用戶才能訪問卓缰,test路由要guest角色才能訪問计呈,login、loginUser都不做攔截征唬,本文講解兩種攔截方式捌显,對test的攔截是在controller對應的方法上加注解,其他是攔截是寫在shiro的配置類中总寒。
預期分析:
tom是有admin角色和所有權限扶歪,所以用tom登錄后,可以訪問edit和admin摄闸,但是不能訪問guest;而cat是guest角色善镰,只有create和query權限,所以不能訪問admin和edit年枕,但是可以訪問guest炫欺。
四、配置shiro
由于springboot還沒有集成shiro熏兄,所以不能直接在application.properties中配置竣稽,需要通過類的方式配置。
核心配置類:
ShiroConfiguration.java
@Configuration
public class ShiroConfiguration {
/**
* 密碼校驗規(guī)則HashedCredentialsMatcher
* 這個類是為了對密碼進行編碼的 ,
* 防止密碼在數(shù)據(jù)庫里明碼保存 , 當然在登陸認證的時候 ,
* 這個類也負責對form里輸入的密碼進行編碼
* 處理認證匹配處理器:如果自定義需要實現(xiàn)繼承HashedCredentialsMatcher
*/
@Bean("hashedCredentialsMatcher")
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
//指定加密方式為MD5
credentialsMatcher.setHashAlgorithmName("MD5");
//加密次數(shù)
credentialsMatcher.setHashIterations(1024);
credentialsMatcher.setStoredCredentialsHexEncoded(true);
return credentialsMatcher;
}
@Bean("authRealm")
@DependsOn("lifecycleBeanPostProcessor")//可選
public AuthRealm authRealm(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher matcher) {
AuthRealm authRealm = new AuthRealm();
authRealm.setAuthorizationCachingEnabled(false);
authRealm.setCredentialsMatcher(matcher);
return authRealm;
}
/**
* 定義安全管理器securityManager,注入自定義的realm
* @param authRealm
* @return
*/
@Bean("securityManager")
public SecurityManager securityManager(@Qualifier("authRealm") AuthRealm authRealm) {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(authRealm);
return manager;
}
/**
* 定義shiroFilter過濾器并注入securityManager
* @param manager
* @return
*/
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager manager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//設置securityManager
bean.setSecurityManager(manager);
//設置登錄頁面
//可以寫路由也可以寫jsp頁面的訪問路徑
bean.setLoginUrl("/login");
//設置登錄成功跳轉(zhuǎn)的頁面
bean.setSuccessUrl("/pages/index.jsp");
//設置未授權跳轉(zhuǎn)的頁面
bean.setUnauthorizedUrl("/pages/unauthorized.jsp");
//定義過濾器
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/index", "authc");
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/loginUser", "anon");
filterChainDefinitionMap.put("/admin", "roles[admin]");
filterChainDefinitionMap.put("/edit", "perms[delete]");
filterChainDefinitionMap.put("/druid/**", "anon");
//需要登錄訪問的資源 , 一般將/**放在最下邊
filterChainDefinitionMap.put("/**", "authc");
bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return bean;
}
/**
* Spring的一個bean , 由Advisor決定對哪些類的方法進行AOP代理 .
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
creator.setProxyTargetClass(true);
return creator;
}
/**
* 配置shiro跟spring的關聯(lián)
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
/**
* lifecycleBeanPostProcessor是負責生命周期的 , 初始化和銷毀的類
* (可選)
*/
@Bean("lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
}
注:這個類每個bean的作用都已在代碼中注釋說明霍弹,這個類就相當于spring整合shiro的spring-shiro.xml中對shiro的配置。
自定義realm:
AutuRealm.java
public class AuthRealm extends AuthorizingRealm{
@Autowired
private UserService userService;
/**
* 為用戶授權
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//獲取前端輸入的用戶信息娃弓,封裝為User對象
User userweb = (User) principals.getPrimaryPrincipal();
//獲取前端輸入的用戶名
String username = userweb.getUsername();
//根據(jù)前端輸入的用戶名查詢數(shù)據(jù)庫中對應的記錄
User user = userService.findByUsername(username);
//如果數(shù)據(jù)庫中有該用戶名對應的記錄典格,就進行授權操作
if (user != null){
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//因為addRoles和addStringPermissions方法需要的參數(shù)類型是Collection
//所以先創(chuàng)建兩個collection集合
Collection<String> rolesCollection = new HashSet<String>();
Collection<String> perStringCollection = new HashSet<String>();
//獲取user的Role的set集合
Set<Role> roles = user.getRoles();
//遍歷集合
for (Role role : roles){
//將每一個role的name裝進collection集合
rolesCollection.add(role.getName());
//獲取每一個Role的permission的set集合
Set<Permission> permissionSet = role.getPermissions();
//遍歷集合
for (Permission permission : permissionSet){
//將每一個permission的name裝進collection集合
perStringCollection.add(permission.getName());
}
//為用戶授權
info.addStringPermissions(perStringCollection);
}
//為用戶授予角色
info.addRoles(rolesCollection);
return info;
}else{
return null;
}
}
/**
* 認證登錄
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//token攜帶了用戶信息
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
//獲取前端輸入的用戶名
String userName = usernamePasswordToken.getUsername();
//根據(jù)用戶名查詢數(shù)據(jù)庫中對應的記錄
User user = userService.findByUsername(userName);
//當前realm對象的name
String realmName = getName();
//鹽值
ByteSource credentialsSalt = ByteSource.Util.bytes(user.getUsername());
//封裝用戶信息,構建AuthenticationInfo對象并返回
AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(user, user.getPassword(),
credentialsSalt, realmName);
return authcInfo;
}
}
注:這個類也有詳細的注釋說明台丛。
這樣就完成了springboot對shiro的整合耍缴,接下來就可以進行測試了砾肺!
五、測試
tom登錄
tom訪問admin
tom訪問test
cat登錄
cat訪問admin
cat訪問test
測試結(jié)果與預期相符防嗡,測試通過变汪,springboot整合shiro成功!
特別說明:
由于設置了MD5加密,所以數(shù)據(jù)庫中存儲的用戶密碼應該是加密后的密文,否則在登錄頁面輸入明文會驗證不通過缴守。假如1234的密文為asdfghjkl佛舱,數(shù)據(jù)庫中存儲的應該是asdfghjkl,在登錄時輸入1234就能驗證通過出皇。
附上明文轉(zhuǎn)密文的代碼:
public static void main(String[] args) {
String hashAlgorithName = "MD5";
String password = "登錄時輸入的密碼";
int hashIterations = 1024;//加密次數(shù)
ByteSource credentialsSalt = ByteSource.Util.bytes("登錄時輸入的用戶名");
Object obj = new SimpleHash(hashAlgorithName, password, credentialsSalt, hashIterations);
System.out.println(obj);
}
若不使用MD5加密
1、添加一個類
public class CredenttiaMatcher extends SimpleCredentialsMatcher{
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
String password = new String(usernamePasswordToken.getPassword());
String dbPassword = (String) info.getCredentials();
return this.equals(password,dbPassword);
}
}
2、將ShiroConfiguration.java中名為"hashedCredentialsMatcher"的bean替換成:
*@Bean("credenttiaMatcher")
public CredenttiaMatcher credenttiaMatcher() {
return new CredenttiaMatcher();
}
將名為"authRealm"的bean替換成:
@Bean("authRealm")
@DependsOn("lifecycleBeanPostProcessor")//可選
public AuthRealm authRealm(@Qualifier("credenttiaMatcher") CredenttiaMatcher matcher) {
AuthRealm authRealm = new AuthRealm();
authRealm.setCredentialsMatcher(matcher);
return authRealm;
}
3徘熔、AuthRealm.java中的doGetAuthenticationInfo方法里面的內(nèi)容替換成:
//=========================未加密版==========================
//token攜帶了用戶登錄的信息
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
//獲取前端輸入的用戶名
String username = usernamePasswordToken.getUsername();
//根據(jù)前端輸入的用戶名查詢數(shù)據(jù)庫中的記錄
User user = userService.findByUsername(username);
//校驗密碼,驗證登錄
return new SimpleAuthenticationInfo(user,user.getPassword(),this.getClass().getName());
完成以上3步就去掉了MD5加密淆党。
以上內(nèi)容屬于個人學習筆記整理酷师,如有錯誤,歡迎批評指正染乌!
我的博客即將搬運同步至騰訊云+社區(qū)山孔,邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=enfrqdpybh7v