開發環境搭建參見《【原】無腦操做:IDEA + maven + SpringBoot + JPA + Thymeleaf實現CRUD及分頁》html
需求:java
① 除了登陸頁面,在地址欄直接訪問其餘URL,均跳轉至登陸頁面mysql
② 登陸涉及賬號和密碼,賬號錯誤提示賬號錯誤,密碼錯誤提示密碼錯誤web
③ 登陸成功跳轉至首頁,首頁顯示登陸者賬號信息,並有註銷賬號功能,點擊註銷退出系統spring
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------sql
分析:數據庫
典型的運用認證權限的需求,考慮使用Shiro。瞭解一下Shiro框架,Apache Shiro是一個強大且易用的Java安全框架,執行身份驗證、受權、密碼和會話管理。apache
關鍵詞彙:json
① Subject:安全術語,本意是「當前的操做用戶」。安全
在安全領域,術語「Subject」能夠是人,也能夠是第三方進程、後臺賬戶(Daemon Account)、定時做業(Corn Job)或其餘相似事物。
它僅僅意味着「當前跟軟件交互的東西」。但考慮到大多數目的和用途,你能夠把它認爲是Shiro的「用戶」概念。
在程序中能輕易得到Subject,容許在任何須要的地方進行安全操做。
每一個Subject對象都必須與一個SecurityManager進行綁定,訪問Subject對象其實都是在與SecurityManager裏的特定Subject進行交互。
② SecurityManager:安全管理器。
Subject表明了當前用戶的安全操做,SecurityManager則管理全部用戶的安全操做。
③ Realm:域,Shiro從Realm獲取安全數據(如用戶、角色、權限),
即SecurityManager要驗證用戶身份,須要從Realm獲取相應用戶進行比較以肯定用戶身份是否合法;
也就是說須要從Realm獲得用戶相應的角色/權限進行驗證用戶是否能進行操做;能夠把Realm當作DataSource,安全數據源。
④ authentication:認證(發音:[ɔ:ˌθentɪ'keɪʃn])
⑤ authorization:受權(發音:[ˌɔ:θərəˈzeɪʃn])
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------
0、數據庫建表init.sql
1 DROP TABLE sys_user; 2 3 CREATE TABLE sys_user 4 ( 5 userid INT AUTO_INCREMENT PRIMARY KEY COMMENT '用戶編號', 6 username VARCHAR(10) NOT NULL COMMENT '用戶名稱', 7 `password` VARCHAR(10) NOT NULL COMMENT '用戶密碼' 8 ); 9 10 INSERT INTO sys_user VALUES(NULL, 'admin', '123'), (NULL, 'test', '456'); 11 12 SELECT * FROM sys_user;
一、編寫項目對象模型文件pom.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 5 <modelVersion>4.0.0</modelVersion> 6 7 <groupId>cn.temptation</groupId> 8 <artifactId>studyShiro</artifactId> 9 <version>1.0-SNAPSHOT</version> 10 11 <parent> 12 <groupId>org.springframework.boot</groupId> 13 <artifactId>spring-boot-starter-parent</artifactId> 14 <version>2.0.4.RELEASE</version> 15 </parent> 16 17 <dependencies> 18 <!-- web --> 19 <dependency> 20 <groupId>org.springframework.boot</groupId> 21 <artifactId>spring-boot-starter-web</artifactId> 22 </dependency> 23 <!-- thymeleaf --> 24 <dependency> 25 <groupId>org.springframework.boot</groupId> 26 <artifactId>spring-boot-starter-thymeleaf</artifactId> 27 </dependency> 28 <!-- spring data jpa --> 29 <dependency> 30 <groupId>org.springframework.boot</groupId> 31 <artifactId>spring-boot-starter-data-jpa</artifactId> 32 </dependency> 33 <!-- mariadb --> 34 <dependency> 35 <groupId>org.mariadb.jdbc</groupId> 36 <artifactId>mariadb-java-client</artifactId> 37 <version>2.2.5</version> 38 </dependency> 39 <!-- shiro --> 40 <dependency> 41 <groupId>org.apache.shiro</groupId> 42 <artifactId>shiro-spring</artifactId> 43 <version>1.4.0</version> 44 </dependency> 45 <!-- 熱啓動 --> 46 <dependency> 47 <groupId>org.springframework.boot</groupId> 48 <artifactId>spring-boot-devtools</artifactId> 49 <optional>true</optional> 50 </dependency> 51 </dependencies> 52 </project>
二、編寫項目配置文件application.properties
1 # 數據庫訪問配置 2 # 對應MariaDB驅動 3 spring.datasource.driverClassName=org.mariadb.jdbc.Driver 4 # 數據源配置 5 spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test 6 spring.datasource.username=root 7 spring.datasource.password=sa 8 # 配置Springboot默認支持的Hikari數據庫鏈接池 9 spring.datasource.type=com.zaxxer.hikari.HikariDataSource 10 spring.datasource.hikari.minimum-idle=5 11 spring.datasource.hikari.maximum-pool-size=15 12 spring.datasource.hikari.auto-commit=true 13 spring.datasource.hikari.idle-timeout=30000 14 spring.datasource.hikari.pool-name=DatebookHikariCP 15 spring.datasource.hikari.max-lifetime=1800000 16 spring.datasource.hikari.connection-timeout=30000 17 spring.datasource.hikari.connection-test-query=SELECT 1 18 # Spring Data JPA配置 19 spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect 20 spring.jpa.properties.hibernate.hbm2ddl.auto=update 21 spring.jpa.show-sql=true 22 spring.jpa.properties.hibernate.format_sql=true 23 # 格式化輸出的json字符串 24 spring.jackson.serialization.indent_output=true 25 # 設置控制檯彩色打印 26 spring.output.ansi.enabled=ALWAYS
三、編寫項目啓動類Application.java
1 package cn.temptation; 2 3 import org.springframework.boot.SpringApplication; 4 import org.springframework.boot.autoconfigure.SpringBootApplication; 5 6 @SpringBootApplication 7 public class Application { 8 public static void main(String[] args) { 9 // SpringBoot項目啓動 10 SpringApplication.run(Application.class, args); 11 } 12 }
四、編寫登陸頁面login.html 和 首頁頁面index.html
登陸頁面:login.html
1 <!DOCTYPE html> 2 <html xmlns:th="http://www.thymeleaf.org"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>系統登陸</title> 6 </head> 7 <body> 8 <div th:text="${msg}" style="color: red"></div> 9 <form action="doLogin" method="post"> 10 賬號:<input type="text" id="txtUsername" name="username" /><br/> 11 密碼:<input type="password" id="txtPassword" name="password" /><br/><br/> 12 <input type="submit" value="提交" /> <input type="reset" value="重置" /> 13 </form> 14 </body> 15 </html>
首頁頁面:index.html
1 <!DOCTYPE html> 2 <html xmlns:th="http://www.thymeleaf.org"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>系統首頁</title> 6 </head> 7 <body> 8 <div th:text="${'歡迎您,' + currentuser}" style="color: red;float: left;"></div> 9 <div style="color: red;float: right;"><a href="doLogout">註銷</a></div> 10 </body> 11 </html>
五、編寫Shiro框架用配置類ShiroConfig.java 和 自定義Realm類MyRealm.java
配置類ShiroConfig.java
1 package cn.temptation.shiro; 2 3 import org.apache.shiro.spring.web.ShiroFilterFactoryBean; 4 import org.apache.shiro.web.mgt.DefaultWebSecurityManager; 5 import org.springframework.beans.factory.annotation.Qualifier; 6 import org.springframework.context.annotation.Bean; 7 import org.springframework.context.annotation.Configuration; 8 9 import java.util.LinkedHashMap; 10 import java.util.Map; 11 12 /** 13 * Shiro配置類 14 */ 15 @Configuration 16 public class ShiroConfig { 17 // 一、建立ShiroFilterFactoryBean 18 @Bean 19 public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) { 20 ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); 21 // 設置安全管理器 22 shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager); 23 24 // 設置登陸跳轉頁面 25 shiroFilterFactoryBean.setLoginUrl("/login"); 26 27 /** 28 * Shiro內置過濾器:實現權限相關的攔截 29 * 經常使用過濾器: 30 * anon(認證用):無需認證(登陸)便可訪問 31 * authc(認證用):必須認證纔可訪問 32 * user(少用):使用rememberMe功能能夠訪問 33 * perms(受權用):必須獲得資源權限纔可訪問 34 * role(受權用):必須獲得角色權限纔可訪問 35 */ 36 Map<String, String> filterMap = new LinkedHashMap<>(); 37 38 // 放行登陸請求 39 filterMap.put("/doLogin", "anon"); 40 41 // 配置退出過濾器,退出代碼Shiro已經實現 42 filterMap.put("/logout", "logout"); 43 44 // 過濾鏈定義,從上向下順序執行,通常將/*放在最下邊 45 filterMap.put("/*", "authc"); 46 47 shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap); 48 49 return shiroFilterFactoryBean; 50 } 51 52 // 二、建立DefaultWebSecurityManager 53 @Bean(name = "securityManager") 54 public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("myRealm") MyRealm myRealm) { 55 DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); 56 57 // 關聯Realm 58 defaultWebSecurityManager.setRealm(myRealm); 59 60 return defaultWebSecurityManager; 61 } 62 63 // 三、建立Realm 64 @Bean(name = "myRealm") 65 public MyRealm getRealm() { 66 return new MyRealm(); 67 } 68 }
自定義Realm類MyRealm.java
1 package cn.temptation.shiro; 2 3 import cn.temptation.dao.UserDao; 4 import cn.temptation.domain.User; 5 import org.apache.shiro.authc.*; 6 import org.apache.shiro.authz.AuthorizationInfo; 7 import org.apache.shiro.realm.AuthorizingRealm; 8 import org.apache.shiro.subject.PrincipalCollection; 9 import org.springframework.beans.factory.annotation.Autowired; 10 11 /** 12 * 自定義Realm 13 */ 14 public class MyRealm extends AuthorizingRealm { 15 @Autowired 16 private UserDao userDao; 17 18 // 受權處理 19 @Override 20 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { 21 return null; 22 } 23 24 // 認證處理 25 @Override 26 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { 27 // 編寫Shiro判斷邏輯,判斷帳號和密碼 28 // 一、判斷帳號 29 UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; 30 31 User user = userDao.findByUsername(token.getUsername()); 32 if (user == null) { 33 // 帳號錯誤,Shiro底層會拋出UnknownAccountException異常 34 return null; 35 } 36 37 // 二、判斷密碼 38 return new SimpleAuthenticationInfo("", user.getPassword(), ""); 39 } 40 }
六、編寫實體類User.java
1 package cn.temptation.domain; 2 3 import javax.persistence.*; 4 5 @Entity 6 @Table(name = "sys_user") 7 public class User { 8 @Id 9 @GeneratedValue(strategy = GenerationType.IDENTITY) 10 @Column(name = "userid") 11 private Integer userid; 12 13 @Column(name = "username") 14 private String username; 15 16 @Column(name = "password") 17 private String password; 18 19 public Integer getUserid() { 20 return userid; 21 } 22 23 public void setUserid(Integer userid) { 24 this.userid = userid; 25 } 26 27 public String getUsername() { 28 return username; 29 } 30 31 public void setUsername(String username) { 32 this.username = username; 33 } 34 35 public String getPassword() { 36 return password; 37 } 38 39 public void setPassword(String password) { 40 this.password = password; 41 } 42 }
七、編寫控制器類UserController.java
1 package cn.temptation.web; 2 3 import org.apache.shiro.SecurityUtils; 4 import org.apache.shiro.authc.IncorrectCredentialsException; 5 import org.apache.shiro.authc.UnknownAccountException; 6 import org.apache.shiro.authc.UsernamePasswordToken; 7 import org.apache.shiro.subject.Subject; 8 import org.springframework.stereotype.Controller; 9 import org.springframework.ui.Model; 10 import org.springframework.web.bind.annotation.RequestMapping; 11 12 @Controller 13 public class UserController { 14 // 訪問登陸頁 15 @RequestMapping("/login") 16 public String login() { 17 return "login"; 18 } 19 20 // 訪問首頁 21 @RequestMapping("/index") 22 public String index() { 23 return "index"; 24 } 25 26 // 登陸處理 27 @RequestMapping("/doLogin") 28 public String doLogin(String username, String password, Model model) { 29 // 使用Shiro編寫認證處理 30 // 一、獲取Subject 31 Subject subject = SecurityUtils.getSubject(); 32 33 // 二、封裝用戶數據 34 UsernamePasswordToken token = new UsernamePasswordToken(username, password); 35 36 // 三、執行登陸 37 try { 38 // 登陸成功 39 subject.login(token); 40 41 // 返回當前用戶的賬號 42 model.addAttribute("currentuser", token.getUsername()); 43 44 return "index"; 45 } catch (UnknownAccountException exception) { 46 // 返回錯誤信息 47 model.addAttribute("msg", "帳號錯誤!"); 48 49 return "login"; 50 } catch (IncorrectCredentialsException exception) { 51 // 返回錯誤信息 52 model.addAttribute("msg", "密碼錯誤!"); 53 54 return "login"; 55 } 56 } 57 58 // 註銷處理 59 @RequestMapping("/doLogout") 60 public String doLogout() { 61 // 一、獲取Subject 62 Subject subject = SecurityUtils.getSubject(); 63 64 // 二、執行註銷 65 try { 66 subject.logout(); 67 } catch (Exception ex) { 68 ex.printStackTrace(); 69 } finally { 70 return "login"; 71 } 72 } 73 }
八、編寫數據訪問接口UserDao.java
1 package cn.temptation.dao; 2 3 import cn.temptation.domain.User; 4 import org.springframework.data.jpa.repository.JpaRepository; 5 import org.springframework.data.jpa.repository.Query; 6 import org.springframework.data.repository.query.Param; 7 8 public interface UserDao extends JpaRepository<User, Integer> { 9 // 根據帳號查詢用戶 10 @Query(value = "SELECT * FROM sys_user WHERE username=:username", nativeQuery = true) 11 User findByUsername(@Param("username") String username); 12 }
九、項目結構
十、運行效果