降龍十八掌之 springboot整合shiro(含MD5加密)

java學習愛好者 2019-05-27 16:21:00

 

開發環境:

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圖。

降龍十八掌之 springboot整合shiro(含MD5加密)

 

圖片發自簡書App

二、添加依賴,配置mybatis

1、用idea新建Spring Initializr項目,項目結構如下:

降龍十八掌之 springboot整合shiro(含MD5加密)

 

圖片發自簡書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());
 }
 }
}

運行結果:

降龍十八掌之 springboot整合shiro(含MD5加密)

 

圖片發自簡書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登錄

降龍十八掌之 springboot整合shiro(含MD5加密)

 

圖片發自簡書App

降龍十八掌之 springboot整合shiro(含MD5加密)

 

圖片發自簡書App

tom訪問admin

降龍十八掌之 springboot整合shiro(含MD5加密)

 

圖片發自簡書App

tom訪問test

降龍十八掌之 springboot整合shiro(含MD5加密)

 

圖片發自簡書App

cat登錄

降龍十八掌之 springboot整合shiro(含MD5加密)

 

圖片發自簡書App

cat訪問admin

降龍十八掌之 springboot整合shiro(含MD5加密)

 

圖片發自簡書App

cat訪問test

降龍十八掌之 springboot整合shiro(含MD5加密)

 

圖片發自簡書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加密。

以上內容屬於個人學習筆記整理,如有錯誤,歡迎批評指正!寫在前面:

寫在後面:

碼字不易看到最後了,那就點個關注唄,只收藏不點關注的都是在耍流氓!

關注並私信我“架構”,免費送一些Java架構資料,先到先得!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章