從零開始構建springboot 2.x Web專案【持續更新】
簡介
文章內容介紹:基於SpringBoot 2.x 的demo,集成了 spring-boot-security、mybatis、druid、redis 等等
讀者按需自取,還有很多未完成的,慢慢來
專案程式碼下載地址: ofollow,noindex">https://github.com/ChaselX/spring-boot-2-demo
使用Maven構建專案
可以用IDEA整合好的Spring Initializr來建立一個SpringBoot專案。

IDEA建立SpringBoot專案

Group和Artifact根據你的專案隨意命名
勾選上自己需要的依賴(不選也沒關係,在Maven中手動加即可)

Spring Boot應用啟動器
Spring Boot提供了很多應用啟動器,分別用來支援不同的功能,因為Spring Boot的自動化配置特性,我們不需考慮專案依賴版本問題,使用Spring Boot的應用啟動器,它能自動幫我們將相關的依賴全部匯入到專案中。
這裡介紹幾個常見的應用啟動器:
- spring-boot-starter: Spring Boot的核心啟動器,包含了自動配置、日誌和YAML
- spring-boot-starter-aop: 支援AOP面向切面程式設計的功能,包括spring-aop和AspecJ
- spring-boot-starter-cache: 支援Spring的Cache抽象
- spring-boot-starter-artermis: 通過Apache Artemis支援JMS(Java Message Service)的API
- spring-boot-starter-data-jpa: 支援JPA
- spring-boot-starter-data-solr: 支援Apache Solr搜尋平臺,包括spring-data-solr
- spring-boot-starter-freemarker: 支援FreeMarker模板引擎
- spring-boot-starter-jdbc: 支援JDBC資料庫
- spring-boot-starter-Redis: 支援Redis鍵值儲存資料庫,包括spring-redis
- spring-boot-starter-security: 支援spring-security
- spring-boot-starter-thymeleaf: 支援Thymeleaf模板引擎,包括與Spring的整合
- spring-boot-starter-web: 支援全棧式web開發,包括tomcat和Spring-WebMVC
- spring-boot-starter-log4j: 支援Log4J日誌框架
- spring-boot-starter-logging: 引入Spring Boot預設的日誌框架Logback
也可以在Spring官網 https://start.spring.io/ 構建專案,勾選上自己需要的依賴即可(之後在Maven裡再加也可以)。
專案的建立成功以後的Maven如下所示:
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> 釋出文章 <packaging>jar</packaging> <name>demo</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <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-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
配置系統基本引數
要訪問mysql資料庫,還需要配置一下系統變數。
預設的系統變數配置檔案是專案當前資料夾的 /src/main/resources
下的 application.properties

但是我更喜歡yml的風格,刪掉這個檔案,在相同的位置建立一個 application.yml
檔案
# 指定埠號 server: port: 8080 spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: mybatis: mapper-locations: classpath*:mapper/*.xml#注意:一定要對應mapper對映xml檔案的所在路徑 type-aliases-package: com.example.demo.model.entity# 注意:對應實體類的路徑
編寫控制層處理HTTP請求
在 /src/main/java/com/example/demo/controller
下建立一個 HelloController.java
package com.example.demo.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author ChaselX * @date 2018/10/6 18:56 */ @RestController @RequestMapping("/") public class HelloController { @GetMapping public String sayHello() { return "Hello SpringBoot!"; } }
執行 DemoApplication.java
,瀏覽器請求 localhost:8080
可以看到如下效果

localhost:8080
通過Mybatis操作資料庫
根據之前的配置
mybatis: mapper-locations: classpath*:mapper/*.xml# 注意:一定要對應mapper對映xml檔案的所在路徑 type-aliases-package: com.example.demo.model.entity # 對應實體類的路徑
在 /src/main/java/com/example/demo/model/entity
下建立實體類 SysUser.java
package com.example.demo.model.entity; /** * @author ChaselX * @date 2018/10/7 17:42 */ public class SysUser { private static final long serialVersionUID = 215517484123587L; /** * 主鍵id */ private Long id; /** * 賬號 */ private String username; /** * 密碼 */ private String password; /** * 姓名 */ private String name; /** * 電話號碼 */ private String mobile; /** * 賬號是否可用 */ private boolean enabled; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getMobile() { return mobile; } public void setMobile(String mobile) { this.mobile = mobile; } public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } }
在 /src/main/java/com/example/demo/mapper
下建立 UserMapper.java
package com.example.demo.mapper; import com.example.demo.model.entity.SysUser; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Select; import java.util.List; /** * @author ChaselX * @date 2018/10/7 17:53 */ public interface UserMapper { @Insert("INSERT INTO user(username, password, name, mobile) VALUES(#{username}, #{password}, #{name}, #{mobile})") //返回插入記錄的主鍵id //@SelectKey(statement = "select LAST_INSERT_ID()", keyProperty = "id", before = false, resultType = Long.class) int add(SysUser user); @Select("SELECT * from user") List<SysUser> getAll(); }
在 DemoApplication.java
上加一個 @MapperScan("com.example.demo.mapper")
註解,這個註解的作用是自動掃描com.example.demo.mapper包下的Mapper,實現並注入到Bean中。
package com.example.demo; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @MapperScan("com.example.demo.mapper") public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
編寫對應的後端控制層與服務層業務邏輯程式碼,檔案位置參考程式碼中的 package
package com.example.demo.service; import com.example.demo.model.entity.SysUser; import java.util.List; /** * @author ChaselX * @date 2018/10/8 8:59 */ public interface UserService { List<User> getAll(); boolean addUser(SysUser user); }
package com.example.demo.service.impl; import com.example.demo.mapper.UserMapper; import com.example.demo.model.entity.SysUser; import com.example.demo.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; /** * @author ChaselX * @date 2018/10/8 8:59 */ @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public List<SysUser> getAll() { return userMapper.getAll(); } @Override public boolean addUser(SysUser user) { return userMapper.add(user) > 0; } }
package com.example.demo.controller; import com.example.demo.model.entity.SysUser; import com.example.demo.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; /** * @author ChaselX * @date 2018/10/8 9:08 */ @RestController @RequestMapping("/users") public class UserController { @Autowired private UserService userService; @GetMapping public ResponseEntity getAllUsers() { return new ResponseEntity<>(userService.getAll(), HttpStatus.OK); } @PostMapping public ResponseEntity addUser(@RequestBody SysUser user) { if (userService.addUser(user)) { return ResponseEntity.ok().build(); } return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("建立使用者失敗!"); } }
現在,可以執行專案利用 postman
對上面的功能介面進行測試了

redis配置與使用(非必須)
這裡不講redis的安裝和啟動,只講專案如何使用redis
在 pom.xml
中加入 spring-boot-starter-data-redis
依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
對系統配置檔案 application.yml
做如下配置
spring: redis: host: 127.0.0.1 port: 6379 timeout: 2000ms database: 0 password: lettuce: pool: max-active:100 # 連線池最大連線數(使用負值表示沒有限制) max-idle: 100 # 連線池中的最大空閒連線 min-idle: 50 # 連線池中的最小空閒連線 max-wait: 6000ms
由於使用的是SpringBoot 2.0推薦的lettuce連線池。SpringBoot 2.0需要手動構建 LettuceConnectionFactory Bean
,
package com.example.demo.common.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.data.redis.RedisProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; /** * @author ChaselX * @date 2018/9/4 10:02 */ @Configuration public class RedisConfig { @Autowired private RedisProperties redisProperties; @Bean public LettuceConnectionFactory lettuceConnectionFactory() { return new LettuceConnectionFactory(new RedisStandaloneConfiguration(redisProperties.getHost(), redisProperties.getPort())); } }
Redis哨兵主從模式
對系統配置檔案 application.yml
做如下配置
spring: redis: timeout: 2000ms database: 1 lettuce: pool: max-active:100 # 連線池最大連線數(使用負值表示沒有限制) max-idle: 100 # 連線池中的最大空閒連線 min-idle: 50 # 連線池中的最小空閒連線 max-wait: 6000ms sentinel: master: mymaster nodes: 10.1.58.117:27379,10.1.58.137:27379 password:
構建哨兵模式的連線工廠,修改 LettuceConnectionFactory
@Bean public LettuceConnectionFactory redisConnectionFactory() { RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration(redisProperties.getSentinel().getMaster(), new HashSet<>(redisProperties.getSentinel().getNodes())); redisSentinelConfiguration.setPassword(RedisPassword.of(redisProperties.getPassword())); return new LettuceConnectionFactory(redisSentinelConfiguration); }
Redis叢集
對系統配置檔案 application.yml
做如下配置
spring: redis: cluster: nodes: - 192.168.1.111:7001 - 192.168.1.112:7001 - 192.168.1.110:7002 - 192.168.1.110:7001 - 192.168.1.111:7002 - 192.168.1.112:7001 password: lettuce: pool: max-active:100 # 連線池最大連線數(使用負值表示沒有限制) max-idle: 100 # 連線池中的最大空閒連線 min-idle: 50 # 連線池中的最小空閒連線 max-wait: 6000ms timeout: 2000ms
構建叢集模式的連線工廠,修改 LettuceConnectionFactory
@Bean public LettuceConnectionFactory redisConnectionFactory(RedisClusterConfiguration redisClusterConfiguration) { return new LettuceConnectionFactory(redisClusterConfiguration); } @Bean public RedisClusterConfiguration redisClusterConfiguration() { RedisClusterConfiguration configuration = new RedisClusterConfiguration(redisProperties.getCluster().getNodes()); configuration.setPassword(RedisPassword.of(redisProperties.getPassword())); return configuration; }
使用Redis
改造之前的 HelloController
對redis功能做簡單的測試
package com.example.demo.controller; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.concurrent.TimeUnit; /** * @author ChaselX * @date 2018/10/6 18:56 */ @RestController @RequestMapping("/") @MapperScan("com.example.demo.mapper") public class HelloController { @Autowired private RedisTemplate<String, String> stringStringRedisTemplate; @GetMapping public String sayHello() { stringStringRedisTemplate.opsForValue().set("Say hello", "Hello SpringBoot From Redis!", 5, TimeUnit.SECONDS); return stringStringRedisTemplate.opsForValue().get("Say hello"); } }
執行專案

資料庫建立使用者及授權(非必須)
對於正式生產環境,你登入到資料庫的往往不會是root使用者,而是通過僅具有特定資料庫許可權的使用者登入資料庫。可以通過下面的SQL語句建立資料庫使用者。
insert into mysql.user(Host,User,Password) values("%","admin",password("admin123")); GRANT ALL ON db_name.* TO admin@% identified by "admin123"; flush privileges;
資料庫連線池druid配置(非必須)
Maven中加入druid依賴
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency>
在 application.yml
中加上如下配置
spring: datasource: druid: # 初始化大小,最小,最大 initialSize: 5 minIdle: 5 maxActive: 20 # 配置獲取連線等待超時的時間 maxWait: 60000 # 配置監控統計攔截的filters,去掉後監控介面sql無法統計,'wall'用於防火牆 filters: stat,wall,slf4j # 開啟PSCache,並且指定每個連線上PSCache的大小 poolPreparedStatements: true maxOpenPreparedStatements: 20 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 # yml方式配置servlet與filter stat-view-servlet: enabled: true # /druid登入賬號 login-username: admin # /druid登入密碼 login-password: admin reset-enable: false web-stat-filter: enabled: true exclusions: /druid/*,*.js,*.gif,*.jpg,*.png,*.css,*.ico url-pattern: /*
配置好 druid
後訪問 {url}/druid
,由於配置了登入賬號和密碼,需要身份認證

認證成功後便可通過監控頁面檢視各項監控資料

基於Spring Security安全框架的的認證與驗證
傳統的登入是通過cookie-session方式實現登入認證,而在前後端分離的情況下,實現使用者鑑權的更好的方式是使用JWT(Java Web Token)
引入Spring Security
在 pom.xml
中加入 Spring Security
依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
在專案中加入 WebSecurityConfig
配置檔案
package com.example.demo.common.config.security; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; /** * @author ChaselX * @date 2018/11/28 16:18 */ @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { // http請求安全配置 @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable().authorizeRequests() .antMatchers("/").permitAll() .anyRequest().authenticated() // 所有請求都需要許可權驗證 .and() .logout().permitAll() .and() .formLogin(); } //// 忽略web靜態資源,若需要 //@Override //public void configure(WebSecurity web) throws Exception { //web.ignoring().antMatchers("/js/**", "/css/**", "/images/**"); //} }
修改一下之前的 HelloController
package com.example.demo.controller; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.concurrent.TimeUnit; /** * @author ChaselX * @date 2018/10/6 18:56 */ @RestController @RequestMapping("/") @MapperScan("com.example.demo.mapper") public class HelloController { @Autowired private RedisTemplate<String, String> stringStringRedisTemplate; @GetMapping public String mainPage() { return "Hello SpringBoot From \"/\""; } @GetMapping("/sayHello") public String sayHello() { stringStringRedisTemplate.opsForValue().set("demo:SayHello", "Hello SpringBoot From Redis!", 5, TimeUnit.SECONDS); return stringStringRedisTemplate.opsForValue().get("demo:SayHello"); } }
執行專案測試

首頁正常展示
訪問 localhost:8080/sayHello
會跳轉到 http://localhost:8080/login
登入頁

Spring Security的安全策略已經生效,但是具體的登入功能還沒有實現
基於Spring Security的登入功能實現
要實現基於Spring Security的登入功能,首先需要定義一個繼承了Spring Security的 UserDetailsService
介面的介面,修改一下之前的UserService
package com.example.demo.service; import com.example.demo.model.entity.SysUser; import org.springframework.security.core.userdetails.UserDetailsService; import java.util.List; /** * @author ChaselX * @date 2018/10/8 8:59 */ public interface UserService extends UserDetailsService { List<SysUser> getAll(); boolean addUser(SysUser user); }
再修改一下介面的實現類 UserServiceImpl
實現 UserDetailsService
介面的 loadUserByUsername()
方法
@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return null; }
因為要返回UserDetails物件,具體方法實現先放在一邊。先看看如何使用這個方法進行使用者認證。在 SpringSecurityConfig
加入以下程式碼
@Autowired private UserDetailsService userServiceImpl; // 屬性名為userServiceImpl對應實現類的名稱 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userServiceImpl); }
密碼自定義加密驗證
使用者的密碼在資料庫中通常是以密文的形式儲存的,為此需要實現一個密碼的自定義驗證,指定Spring Security使用什麼加密規則對密碼進行驗證,建立一個bean bCryptPasswordEncoder
package com.example.demo.common.config.security; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; /** * @author ChaselX * @date 2018/11/28 19:21 */ @Configuration public class BaseConfig { @Bean public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); } }
修改 WebSecurityConfig
的程式碼指定 passwordEncoder
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userServiceImpl).passwordEncoder(bCryptPasswordEncoder); }
由於使用了密碼加密驗證,需要修改一下新增使用者那裡的邏輯,在插入資料庫之前先對密碼做BCrypt加密
package com.example.demo.service.impl; import com.example.demo.mapper.UserMapper; import com.example.demo.model.entity.SysUser; import com.example.demo.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; /** * @author ChaselX * @date 2018/10/8 8:59 */ @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Autowired private BCryptPasswordEncoder encoder; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return null; } @Override public List<SysUser> getAll() { return userMapper.getAll(); } @Override public boolean addUser(SysUser sysUser) { sysUser.setPassword(encoder.encode(sysUser.getPassword())); return userMapper.add(sysUser) > 0; } }
這樣便實現了密碼的自定義驗證
基於RBAC的使用者、角色、許可權
雖然實現了加密驗證,但是卻沒有定義許可權驗證相關的使用者、角色、許可權
要使用Spring Security安全框架,由於 UserDetailsService.loadUserByUsername()
返回的是一個 UserDetails
型別的物件。 UserDetails
介面中最重要的是 getAuthorities()
方法,使用者所具有的所有許可權都定義在裡面。因此需要做些處理,從資料庫獲取系統使用者,並根據相關的角色許可權來構造 UserDetails
的 authorities
屬性,為此首先需要定義好系統的使用者、角色、許可權實體表與它們之間的關係表。使用者實體類已經定義好,還有角色、許可權、使用者角色以及角色許可權未定義。
package com.example.demo.model.entity; import java.util.Date; /** * 角色實體類 * * @author ChaselX * @date 2018/12/1 16:05 */ public class Role { /** * 角色的authority字首 */ public static final String PREFIX = "ROLE_"; /** * 主鍵id */ private Long id; /** * 角色代號 */ private String code; /** * 角色名 */ private String name; /** * 備註 */ private String remark; private Long operator; private Date operateTime; // 省略get/set方法程式碼 }
package com.example.demo.model.entity; import java.util.Date; /** * 許可權實體類 * * @author ChaselX * @date 2018/12/1 16:00 */ public class Permission { /** * 主鍵id */ private Long id; /** * 許可權編碼 */ private String code; /** * 許可權名稱 */ private String name; /** * 操作人 */ private String operator; /** * 操作時間 */ private Date operateTime; // 省略get/set方法程式碼 }
package com.example.demo.model.entity; import java.util.Date; /** * 使用者-角色關係表 * * @author ChaselX * @date 2018/12/1 16:17 */ public class UserRole { private Long id; private Long userId; private Long roleId; private Long operator; private Date operateTime; // 省略get/set方法程式碼 }
package com.example.demo.model.entity; import java.util.Date; /** * 角色-許可權關係表 * * @author ChaselX * @date 2018/12/1 16:25 */ public class RolePermission { private Long id; private Long roleId; private Long permissionId; private Long operator; private Date operateTime; // 省略get/set方法程式碼 }
資料庫相關建表這邊就不贅述了,按照基本的主鍵id自增,引數駝峰命名法轉下劃線命名法即可,若有需要日後再補充。
為了減少資料庫的訪問次數,一次性將使用者相關的資訊(角色、許可權)查詢出來,封裝一個 SysUserVO
類
package com.example.demo.model.vo; import com.example.demo.model.entity.Permission; import com.example.demo.model.entity.Role; import com.example.demo.model.entity.SysUser; import java.util.List; /** * @author ChaselX * @date 2018/12/1 16:51 */ public class SysUserVO extends SysUser { private List<Role> roles; private List<Permission> permissions; public List<Role> getRoles() { return roles; } public void setRoles(List<Role> roles) { this.roles = roles; } public List<Permission> getPermissions() { return permissions; } public void setPermissions(List<Permission> permissions) { this.permissions = permissions; } }
在 UserMapper
新增一個查詢使用者詳細資訊的方法 getDetailsByUsername
package com.example.demo.mapper; import com.example.demo.model.entity.SysUser; import com.example.demo.model.vo.SysUserVO; import org.apache.ibatis.annotations.*; import java.util.List; /** * @author ChaselX * @date 2018/10/7 17:53 */ public interface UserMapper { @Insert("INSERT INTO user(username, password, name, mobile) VALUES(#{username}, #{password}, #{name}, #{mobile})") //返回插入記錄的主鍵id //@SelectKey(statement = "select LAST_INSERT_ID()", keyProperty = "id", before = false, resultType = Long.class) int add(SysUser sysUser); @Select("SELECT * from user") List<SysUser> getAll(); @Select("SELECT * FROM user WHERE username = #{username}") @Results({ @Result(property = "roles", column = "user_id", many = @Many(select = "com.example.demo.mapper.RoleMapper.getRolesByUserId")), @Result(property = "permissions", column = "user_id", many = @Many(select = "com.example.demo.mapper.PermissionMapper.getPermissionsByUserId")) }) SysUserVO getDetailsByUsername(String username); }
注意,由於 SysUserVO
的兩個欄位roles與permissions是集合型別的,所以用到了 @Results
、 @Result
與 @Many
註解,更全面的說明可參考 mybatis官方文件(需翻牆) 。
@Many
註解的 select
屬性表明select引用的來源分別為 com.example.demo.mapper.RoleMapper
下的 getRolesByUserId
方法與 com.example.demo.mapper.PermissionMapper
下的 getPermissionsByUserId
方法
package com.example.demo.mapper; import com.example.demo.model.entity.Role; import org.apache.ibatis.annotations.Select; import java.util.List; /** * @author ChaselX * @date 2018/12/4 14:36 */ public interface RoleMapper { @Select("select r.id, r.code, r.name, r.remark from role r where r.id in (select ur.role_id from user_role ur where ur.user_id = #{userId})") List<Role> getRolesByUserId(Long userId); }
package com.example.demo.mapper; import com.example.demo.model.entity.Permission; import org.apache.ibatis.annotations.Select; import java.util.List; /** * @author ChaselX * @date 2018/12/4 14:58 */ public interface PermissionMapper { @Select("SELECT p.id, p.code, p.name FROM permission p WHERE p.id IN (" + "SELECT rp.permission_id FROM role_permission rp WHERE rp.role_id in(" + "SELECT ur.role_id FROM user_role ur WHERE ur.user_id = #{userId}))") List<Permission> getPermissionsByUserId(Long userId); }
這些都完成了以後就可以動手實現前面放置在一邊的 UserServiceImpl
的 loadUserByUsername
方法了
@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { SysUserVO userVO = userMapper.getDetailsByUsername(username); List<GrantedAuthority> authorities = new ArrayList<>(); for (Role role : userVO.getRoles() ) { authorities.add(new SimpleGrantedAuthority(Role.PREFIX + role.getCode())); } for (Permission permission : userVO.getPermissions() ) { authorities.add(new SimpleGrantedAuthority(permission.getCode())); } return new User(userVO.getUsername(), userVO.getPassword(), authorities); }
現在登入功能已經實現,可以執行專案進行登入功能測試了(tips:在登入之前需要先在使用者表中加入使用BCrypt加密的使用者記錄),專案啟動後訪問 http://localhost:8080/login 會跳轉到登入介面

輸入使用者名稱和密碼,登入成功後會返回系統首頁

未完待續 未完待續 未完待續 未完待續 未完待續 未完待續 未完待續 未完待續 未完待續
aes加密傳輸登入密碼
在非https的情況下,若無特殊處理,使用者的登入密碼會以明文的方式傳輸給後端。因此需要對使用者密碼進行加密傳輸,保證請求報文即使被擷取,也不會洩露使用者的密碼。前後端加解密流程如下( 圖片引用 ):

呼叫介面獲取動態加密祕鑰
在客戶端向後端post登入資訊之前,先呼叫介面獲取動態加密祕鑰,前端生成隨機祕鑰,後端會把快取放進redis裡,為了安全性考慮,快取的有效期設定為5s
客戶端收到動態加密祕鑰後,通過祕鑰對密碼做AES加密,將登陸資訊通過POST請求傳送給後端
jwt動態重新整理
資料庫分頁查詢
往專案的pom.xml里加入
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>latest version</version> </dependency>