springboot整合shiro(含MD5加密)
寫在前面:
關於shiro介紹以及shiro整合spring,我在另一篇文章中已詳細介紹,此處不作說明,請參考spring整合shiro。
開發環境:
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
專案開始:
一、資料庫設計:
注:資料庫三張表和spring整合shiro中的一模一樣,在那邊已經詳細說明,這裡直接大家看下三張表的ER圖。
圖片發自簡書App
二、新增依賴,配置mybatis
1、
圖片發自簡書App
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;這裡將採用另一種方式,三個實體類,設定實體類的關聯關係。 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()); } } }
執行結果:
圖片發自簡書App
資料庫中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 (登入成功跳轉的頁面)
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h1>歡迎登入,${user.username}</h1> </body> </html>
unauthorized.jsp (無權訪問跳轉的頁面)
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>unauthorized</title> </head> <body> unauthorized! </body> </html>
現在說一下要求: 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 { /** * 密碼校驗規則HashedCredentialsMatcher * 這個類是為了對密碼進行編碼的 , * 防止密碼在資料庫裡明碼儲存 , 當然在登陸認證的時候 , * 這個類也負責對form裡輸入的密碼進行編碼 * 處理認證匹配處理器:如果自定義需要實現繼承HashedCredentialsMatcher */ @Bean("hashedCredentialsMatcher") public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); //指定加密方式為MD5 credentialsMatcher.setHashAlgorithmName("MD5"); //加密次數 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"); //設定登入成功跳轉的頁面 bean.setSuccessUrl("/pages/index.jsp"); //設定未授權跳轉的頁面 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的關聯 * @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(); //根據前端輸入的使用者名稱查詢資料庫中對應的記錄 User user = userService.findByUsername(username); //如果資料庫中有該使用者名稱對應的記錄,就進行授權操作 if (user != null){ SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //因為addRoles和addStringPermissions方法需要的引數型別是Collection //所以先建立兩個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(); //根據使用者名稱查詢資料庫中對應的記錄 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登入
圖片發自簡書App
圖片發自簡書App
tom訪問admin
圖片發自簡書App
tom訪問test
圖片發自簡書App
cat登入
圖片發自簡書App
cat訪問admin
圖片發自簡書App
cat訪問test
圖片發自簡書App
測試結果與預期相符,測試通過,springboot整合shiro成功!
特別說明:
由於設定了MD5加密,所以資料庫中儲存的使用者密碼應該是加密後的密文,否則在登入頁面輸入明文會驗證不通過。假如1234的密文為asdfghjkl,資料庫中儲存的應該是asdfghjkl,在登入時輸入1234就能驗證通過。 附上明文轉密文的程式碼:
public static void main(String[] args) { String hashAlgorithName = "MD5"; String password = "登入時輸入的密碼"; int hashIterations = 1024;//加密次數 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方法裡面的內容替換成:
//=========================未加密版========================== //token攜帶了使用者登入的資訊 UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token; //獲取前端輸入的使用者名稱 String username = usernamePasswordToken.getUsername(); //根據前端輸入的使用者名稱查詢資料庫中的記錄 User user = userService.findByUsername(username); //校驗密碼,驗證登入 return new SimpleAuthenticationInfo(user,user.getPassword(),this.getClass().getName());
完成以上3步就去掉了MD5加密。
以上內容屬於個人學習筆記整理,如有錯誤,歡迎批評指正!
本文參與騰訊雲自媒體分享計劃,歡迎正在閱讀的你也加入,一起分享。
發表於 2018-05-18