什麼是SpringSecurity
?java
Spring Security是一個可以爲基於Spring的企業應用系統提供聲明式的安全訪問控制解決方案的安全框架。它提供了一組能夠在Spring應用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反轉Inversion of Control ,DI:Dependency Injection 依賴注入)和AOP(面向切面編程)功能,爲應用系統提供聲明式的安全訪問控制功能,減小了爲企業系統安全控制編寫大量重複代碼的工做。
以上來介紹來自wiki
,比較官方。mysql
用本身的話 簡單介紹一下,Spring Security
基於 Servlet 過濾器鏈的形式,爲咱們的web項目提供認證
與受權
服務。它來自於Spring
,那麼它與SpringBoot
整合開發有着自然的優點,目前與SpringSecurity
對應的開源框架還有shiro
。接下來我將經過一個簡單的例子帶你們來認識SpringSecurity
,而後經過分析它的源碼帶你們來認識一下SpringSecurity
是如何工做,從一個簡單例子入門,你們由淺入深的瞭解學習SpringSecurity
。web
一般你們在作一個後臺管理的系統的時候,應該採用session
判斷用戶是否登陸。我記得我在沒有接觸學習SpringSecurity與shiro以前。對於用戶登陸功能實現一般是以下:spring
public String login(User user, HttpSession session){ //一、根據用戶名或者id從數據庫讀取數據庫中用戶 //二、判斷密碼是否一致 //三、若是密碼一致 session.setAttribute("user",user); //四、不然返回登陸頁面 } 對於以後那些須要登陸以後才能訪問的url,經過SpringMvc的攔截器中的#preHandle來判斷session中是否有user對象 若是沒有 則返回登陸頁面 若是有, 則容許訪問這個頁面。
可是在SpringSecurity
中,這一些邏輯已經被封裝起來,咱們只須要簡單的配置一下就能使用。sql
接下來我經過一個簡單例子你們認識一下SpringSecurity
數據庫
本文基於SpringBoot
,若是你們對SpringBoot不熟悉的話能夠看看我以前寫的SpringBoot入門系列apache
我使用的是:編程
<?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> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.yukong</groupId> <artifactId>springboot-springsecurity</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot-springsecurity</name> <description>springboot-springsecurity study</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</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>2.0.1</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> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-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>
配置一下數據庫 以及MyBatis
安全
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/db_test?useUnicode=true&characterEncoding=utf8 password: abc123 mybatis: mapper-locations: classpath:mapper/*.xml
這裏我用的MySQL8.0
你們注意一下 MySQL8.0
的數據庫驅動的類的包更名了springboot
在前面我有講過SpringBoot中如何整合Mybatis,在這裏我就不累述,有須要的話看這篇文章
user.sql
CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵', `username` varchar(32) NOT NULL COMMENT '用戶名', `svc_num` varchar(32) DEFAULT NULL COMMENT '用戶號碼', `password` varchar(100) DEFAULT NULL COMMENT '密碼', `cust_id` bigint(20) DEFAULT NULL COMMENT '客戶id 1對1', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
對應的UserMapper.java
package com.yukong.mapper; import com.yukong.entity.User; /** * * @author yukong * @date 2019-04-11 16:50 */ public interface UserMapper { int insertSelective(User record); User selectByUsername(String username); }
UserMapper.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.yukong.mapper.UserMapper"> <resultMap id="BaseResultMap" type="com.yukong.entity.User"> <id column="id" jdbcType="BIGINT" property="id" /> <result column="username" jdbcType="VARCHAR" property="username" /> <result column="svc_num" jdbcType="VARCHAR" property="svcNum" /> <result column="password" jdbcType="VARCHAR" property="password" /> <result column="cust_id" jdbcType="BIGINT" property="custId" /> </resultMap> <sql id="Base_Column_List"> id, username, svc_num, `password`, cust_id </sql> <select id="selectByUsername" parameterType="java.lang.String" resultMap="BaseResultMap"> select <include refid="Base_Column_List" /> from user where username = #{username,jdbcType=VARCHAR} </select> <insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.yukong.entity.User" useGeneratedKeys="true"> insert into user <trim prefix="(" suffix=")" suffixOverrides=","> <if test="username != null"> username, </if> <if test="svcNum != null"> svc_num, </if> <if test="password != null"> `password`, </if> <if test="custId != null"> cust_id, </if> </trim> <trim prefix="values (" suffix=")" suffixOverrides=","> <if test="username != null"> #{username,jdbcType=VARCHAR}, </if> <if test="svcNum != null"> #{svcNum,jdbcType=VARCHAR}, </if> <if test="password != null"> #{password,jdbcType=VARCHAR}, </if> <if test="custId != null"> #{custId,jdbcType=BIGINT}, </if> </trim> </insert> </mapper>
在這裏咱們定義了兩個方法。
國際慣例ctrl+shift+t
建立mapper的測試方法,而且插入一條記錄
package com.yukong.mapper; import com.yukong.SpringbootSpringsecurityApplicationTests; import com.yukong.entity.User; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.password.PasswordEncoder; import static org.junit.Assert.*; /** * @author yukong * @date 2019-04-11 16:53 */ public class UserMapperTest extends SpringbootSpringsecurityApplicationTests { @Autowired private UserMapper userMapper; @Test public void insert() { User user = new User(); user.setUsername("yukong"); user.setPassword("abc123"); userMapper.insertSelective(user); } }
運行測試方法,而且成功插入一條記錄。
建立UserController.java
package com.yukong.controller; import com.yukong.entity.User; import com.yukong.mapper.UserMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author yukong * @date 2019-04-11 15:22 */ @RestController public class UserController { @Autowired private UserMapper userMapper; @RequestMapping("/user/{username}") public User hello(@PathVariable String username) { return userMapper.selectByUsername(username); } }
這個方法就是根據用戶名去數據庫查找用戶詳細信息。
啓動。由於咱們以前插入過一條username=yukong的記錄,因此咱們查詢一下,訪問127.0.0.1:8080/user/yukong
[圖片上傳失敗...(image-ea02ac-1554981869345)]
咱們能夠看到 咱們被重定向到了一個登陸界面,這也是咱們以前引入的spring-boot-security-starter
起做用了。
你們可能想問了,用戶名跟密碼是什麼,用戶名默認是user
,密碼在啓動的時候已經經過日誌打印在控制檯了。
如今咱們輸入用戶跟密碼而且登陸。就能夠成功訪問咱們想要訪問的接口。
從這裏咱們能夠知道,我只須要引入了Spring-Security
的依賴,它就開始生效,而且保護咱們的接口了,可是如今有一個問題就是,它的用戶名只能是user而且密碼是經過日誌打印在控制檯,可是咱們但願它能經過數據來訪問咱們的用戶而且判斷登陸。
其實想實現這個功能也很簡單。這裏咱們須要瞭解兩個接口。
因此,咱們須要將咱們的User.java實現這個接口
package com.yukong.entity; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; /** * * @author yukong * @date 2019-04-11 16:50 */ public class User implements UserDetails { /** * 主鍵 */ private Long id; /** * 用戶名 */ private String username; /** * 用戶號碼 */ private String svcNum; /** * 密碼 */ private String password; /** * 客戶id 1對1 */ private Long custId; public Long getId() { return id; } public void setId(Long id) { this.id = id; } @Override public String getUsername() { return username; } @Override public boolean isAccountNonExpired() { return false; } @Override public boolean isAccountNonLocked() { return false; } @Override public boolean isCredentialsNonExpired() { return false; } @Override public boolean isEnabled() { return false; } public void setUsername(String username) { this.username = username; } public String getSvcNum() { return svcNum; } public void setSvcNum(String svcNum) { this.svcNum = svcNum; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { // 這裏咱們沒有用到權限,因此返回一個默認的admin權限 return AuthorityUtils.commaSeparatedStringToAuthorityList("admin"); } @Override public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Long getCustId() { return custId; } public void setCustId(Long custId) { this.custId = custId; } }
接下來咱們再看看UserDetailsService
它只有一個方法的聲明,就是經過用戶名去查找用戶信息,從這裏咱們應該知道了,SpringSecurity回調UserDetails#loadUserByUsername去獲取用戶,可是它不知道用戶信息存在哪裏,因此定義成接口,讓使用者去實現。在咱們這個項目用 咱們的用戶是存在了數據庫中,因此咱們須要調用UserMapper的方法去訪問數據庫查詢用戶信息。這裏咱們新建一個類叫MyUserDetailsServiceImpl
package com.yukong.config; import com.yukong.mapper.UserMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; /** * @author yukong * @date 2019-04-11 17:35 */ @Service public class MyUserDetailServiceImpl implements UserDetailsService { @Autowired private UserMapper userMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return userMapper.selectByUsername(username); } }
而後新建一個類去把咱們的UserDetailsService
配置進去
這裏咱們新建一個SecurityConfig
package com.yukong.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; /** * @author yukong * @date 2019-04-11 15:08 */ @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { // 配置UserDetailsService 跟 PasswordEncoder 加密器 auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); auth.eraseCredentials(false); } }
在這裏咱們還配置了一個PasswordEncoder
加密咱們的密碼,你們都知道密碼明文存數據庫是很不安全的。
接下里咱們插入一條記錄,須要注意的是 密碼須要加密。
package com.yukong.mapper; import com.yukong.SpringbootSpringsecurityApplicationTests; import com.yukong.entity.User; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.password.PasswordEncoder; import static org.junit.Assert.*; /** * @author yukong * @date 2019-04-11 16:53 */ public class UserMapperTest extends SpringbootSpringsecurityApplicationTests { @Autowired private PasswordEncoder passwordEncoder; @Autowired private UserMapper userMapper; @Test public void insert() { User user = new User(); user.setUsername("yukong"); user.setPassword(passwordEncoder.encode("abc123")); userMapper.insertSelective(user); } }
接下來啓動程序,而且登陸,此次只須要輸入插入到數據中的那條記錄的用戶名跟密碼便可。
在這裏一節中,咱們瞭解到如何使用springsecurity 完成一個登陸功能,接下咱們將經過分析源碼來了解爲何須要這個配置,以及SpringSecurity的工做原理是什麼。