數據庫結構
1. 項目目錄結構
2. pom.xml 添加依賴
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>cn.xej</groupId> <artifactId>springboot-shiro</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot-shiro</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <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>2.1.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.1</version> </dependency> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.13</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
3. application.yml 配置文件
spring: datasource: url: jdbc:mysql://localhost:3306/tb_shiro?useUnicode=true&&characterEncoding=utf-8&serverTimezone=GMT username: root password: 123456 type: com.alibaba.druid.pool.DruidDataSource thymeleaf: cache: false mybatis: mapper-locations: classpath:mapping/*.xml type-aliases-package: cn.xej.pojo configuration: map-underscore-to-camel-case: true # 該配置就是將帶有下划線的表字段映射為駝峰格式的實體類屬性
4. User 實體類
package cn.xej.pojo; import lombok.Data; @Data public class User { private String userId; private String password; private String name; }
5. UserDao接口
package cn.xej.mapper; import cn.xej.pojo.User; import org.springframework.stereotype.Repository; import java.util.List; @Repository public interface UserDao { public User findByUserId(String userId); public List<String> queryRolesIdByUserId(String userId); }
6. 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="cn.xej.mapper.UserDao"> <select id="findByUserId" resultType="User"> SELECT * FROM tb_user WHERE user_id=#{userId} </select> <select id="queryRolesIdByUserId" resultType="String"> SELECT ur.role_id FROM tb_user AS u,tb_user_role AS ur WHERE u.user_id = ur.user_id AND u.user_id = #{userId} </select> </mapper>
7. UserService接口
package cn.xej.service; import cn.xej.pojo.User; import java.util.List; public interface UserService { // 根據用戶id查詢該用戶 public User findByUserId(String userId); // 根據用戶id獲取該用戶角色 public List<String> getRolesIdByUserId(String userId); }
8. UserServiceimpl接口實現類
package cn.xej.service.impl; import cn.xej.mapper.UserDao; import cn.xej.pojo.User; import cn.xej.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class UserServiceimpl implements UserService { @Autowired private UserDao userDao; @Override public User findByUserId(String userId) { return userDao.findByUserId(userId); } @Override public List<String> getRolesIdByUserId(String userId) { return userDao.queryRolesIdByUserId(userId); } }
9. SpringbootShiroApplication 啟動類添加包掃描注解
package cn.xej; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @MapperScan("cn.xej.mapper") // 配置一個或多個包路徑,自動的掃描這些包路徑下的類,自動的為它們生成代理類。 public class SpringbootShiroApplication { public static void main(String[] args) { SpringApplication.run(SpringbootShiroApplication.class, args); } }
10. RespObj
package cn.xej.common; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class RespObj { private Integer code; private String message; private Object data; public static RespObj build(Integer code,String message,Object data){ return new RespObj(code,message,data); } }
11. SysController(路由跳轉控制器)
package cn.xej.controller; import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.Subject; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class SysController { // 進入到登錄頁面 @RequestMapping({"/","/welcome"}) public String welcome(){ return "login"; } // 進入到首頁(登錄成功或游客) @RequestMapping("/index") public String index(){ return "index"; } }
12. UserController(控制器)
package cn.xej.controller; import cn.xej.common.RespObj; import cn.xej.pojo.User; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; 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.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpSession; @Controller @RequestMapping("/user") public class UserController { // 登錄用戶, 返回json數據,因此要加 @ResponseBody注解 @PostMapping("/toLogin") @ResponseBody public RespObj toLogin(String userId, String password, HttpSession session){ // 1. 獲取 主體 subject Subject subject = SecurityUtils.getSubject();
// 2. 將賬號和密碼進行封裝 UsernamePasswordToken token = new UsernamePasswordToken(userId,password); // 3. shiro認證,進入自定義UserRealm中 try { subject.login(token);
// 認證成功,將用戶名存到session中,返回json數據 session.setAttribute("currentUserName",((User)subject.getPrincipal()).getName()); return RespObj.build(200,"ok",null); } catch (Exception e) { System.out.println("賬號或密碼錯誤"); return RespObj.build(500,"賬號或密碼錯誤",null); } }
// 注銷用戶 @PostMapping("/logout") @ResponseBody public RespObj logout(){ Subject subject = SecurityUtils.getSubject(); subject.logout(); return RespObj.build(200,"ok",null); } @RequestMapping("/add") public String add(){ return "pages/add"; } @RequestMapping("/update") public String update(){ return "pages/update"; } }
13. 自定義UserRealm
package cn.xej.config; import cn.xej.pojo.User; import cn.xej.service.UserService; 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 java.util.List; public class UserRealm extends AuthorizingRealm { @Autowired private UserService userService; // 授權 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("授權"); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); User user = (User) principalCollection.getPrimaryPrincipal(); List<String> roles = userService.getRolesIdByUserId(user.getUserId()); info.addRoles(roles); return info; } // 認證 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("認證"); UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken; User user = userService.findByUserId(token.getUsername()); if(user==null){ return null; } return new SimpleAuthenticationInfo(user,user.getPassword(),getName());
// 第一個參數,將會傳到授權中進行獲取,比如 User user = (User) principalCollection.getPrimaryPrincipal();
// 第二個參數,是該用戶數據庫里的密碼 // 第三個參數,是當前類名 } }
14. Shiro配置文件(ShiroConfig)
package cn.xej.config; import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; import org.apache.shiro.mgt.SecurityManager; 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.autoproxy.DefaultAdvisorAutoProxyCreator; 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(name="xxx") xxx是自定義的方法名 // 配置自定義Realm @Bean public UserRealm userRealm(){ return new UserRealm(); } // 配置安全管理器,把自定義的Realm添加到安全管理器中 @Bean public DefaultWebSecurityManager defaultWebSecurityManager(UserRealm userRealm){ DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); defaultWebSecurityManager.setRealm(userRealm); return defaultWebSecurityManager; } // 配置Filter工廠,設置對應的過濾條件和跳轉條件,把安全管理器添加到Filter工廠中 @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager); shiroFilterFactoryBean.setLoginUrl("/welcome"); //設置進入登錄頁面的url shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
shiroFilterFactoryBean.setSuccessUrl("/index"); //設置登錄成功url
Map<String,String> map = new LinkedHashMap<String, String>();
// index和 user/toLogin兩個路徑不用攔截 map.put("/index","anon"); map.put("/user/toLogin","anon"); map.put("/**","authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(map); return shiroFilterFactoryBean; } @Bean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); // 強制使用cglib,防止重復代理和可能引起代理出錯的問題 defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); return defaultAdvisorAutoProxyCreator; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; } @Bean public ShiroDialect shiroDialect(){ return new ShiroDialect(); } }
15. login.html頁面
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script> </head> <body> <form> 賬號 <input type="text" name="userId" id="userId"><br/> 密碼 <input type="password" name="password" id="password"><br/> <button type="button" onclick="login()">登錄</button> </form> <script> function login() { var userId = $('#userId').val(); var password = $('#password').val(); $.ajax({ url: '/user/toLogin', type: 'post', data: { userId: userId, password: password }, success: function (data) { if(data.code===200){ alert('登錄成功'); location.href="/index"; // 這里還是走控制器,讓控制器來返回具體頁面 } } }) } </script> </body> </html>
16. index.html (首頁) (當用戶沒登錄時,三元表達式顯示游客名字,登錄時顯示該用戶名字,並通過shiro標簽執行UserRealm中的授權方法,然后通過該用戶的id獲取他的角色)
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script> </head> <body> <p>首頁</p><br> <a href="javascript:;" th:text="${session.currentUserName}!=null ? ${session.currentUserName} : '游客'"></a><br> <a href="welcome">登錄</a> <a href="javascript:;" class="logout">注銷</a><br> <div shiro:hasRole="admin"> <a href="user/add">添加教師</a><br> <a href="user/update">更新教師</a><br> </div> <div shiro:hasAnyRoles="admin,teacher"> <a href="user/add">添加學生</a><br> <a href="user/update">更新學生</a><br> </div> <div shiro:hasRole="student"> <a href="user/update">普通學生</a><br> </div> <a href="">游客</a><br> <script> $('.logout').click(function () { $.ajax({ url: '/user/logout', type: 'post', success: function (data) { if(data.code===200){ alert("注銷成功"); location.href="/index"; } } }) }) </script> </body> </html>
17. 實現用戶密碼加密
1. 首先在shiro 配置文件中 添加解密規則,如下面的解密憑證器,然后放到UserRealm中
// shiro 解密憑證器 @Bean public HashedCredentialsMatcher hashedCredentialsMatcher(){ HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashAlgorithmName("MD5"); hashedCredentialsMatcher.setHashIterations(1024); return hashedCredentialsMatcher; } // 配置自定義Realm @Bean public UserRealm userRealm(HashedCredentialsMatcher hashedCredentialsMatcher){ UserRealm userRealm = new UserRealm(); userRealm.setCredentialsMatcher(hashedCredentialsMatcher); return userRealm; }
2.然后在UserRealm 認證方法中,返回4個參數
// 認證 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("認證"); UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken; User user = userService.findByUserId(token.getUsername()); if(user==null){ return null; } return new SimpleAuthenticationInfo(user,user.getPassword(), ByteSource.Util.bytes(user.getUserId()),getName()); // 第一個參數,將會傳到授權中進行獲取,比如 User user = (User) principalCollection.getPrimaryPrincipal(); // 第二個參數,是該用戶數據庫里的密碼
// 第三個參數,是salt,這里我是用用戶id當作鹽
// 第四個參數,是當前類名
}
3. 最后寫個測試方法,產生密碼加密
“123” 是用戶密碼
“teacher1” 是加密的鹽
1024 是加密次數
String password1 = new SimpleHash("MD5","123","teacher1",1024).toString(); System.out.println("password1 "+password1);