什麼是SpringSecurity
?php
Spring Security是一個可以爲基於Spring的企業應用系統提供聲明式的安全訪問控制解決方案的安全框架。它提供了一組能夠在Spring應用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反轉Inversion of Control ,DI:Dependency Injection 依賴注入)和AOP(面向切面編程)功能,爲應用系統提供聲明式的安全訪問控制功能,減小了爲企業系統安全控制編寫大量重複代碼的工做。java
以上來介紹來自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中如何整合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的工做原理是什麼。