Spring Security:概述
前言
在之前介绍过了Shiro之后,有好多粉丝问SpringSecurity在Spring Boot中怎么集成。这个系列我们就和大家分享下有关这方面的知识。
本节大纲
一、什么是SpringSecurity?
二、常用安全框架
一、什么是Spring Security?
SpringSecurity是基于Spring AOP和Servlet过滤器的安全框架。它提供全面的安全性解决方案,同时在Web 请求级和方法调用级处理身份确认和授权。在 Spring Framework 基础上,Spring Security 充分利用了依赖注入(DI,Dependency Injection)和面向切面编程(AOP)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。它是一个轻量级的安全框架,它确保基于Spring的应用程序提供身份验证和授权支持。它与Spring MVC有很好地集成,并配备了流行的安全算法实现捆绑在一起。安全主要包括两个操作“认证”与“验证”(有时候也会叫做权限控制)。“认证”是为用户建立一个其声明的角色的过程,这个角色可以一个用户、一个设备或者一个系统。“验证”指的是一个用户在你的应用中能够执行某个操作。在到达授权判断之前,角色已经在身份认证过程中建立了。
我们简单来理解一下上面这段话:
第一:什么是Spring Security ?
Spring Security是一个安全框架。
第二:Spring Security核心功能?
(1)认证(你是谁,用户/设备/系统)
(2)验证(你能干什么,也叫权限控制/授权,允许执行的操作)
(3)攻击防护(防止伪造身份)
第三:Spring Security原理技术
Filter、Servlet、Spring DI、SpringAOP
二、常用安全框架
目前常用的安全框架主要是Spring Security和Apache Shiro,它们的区别是什么呢?
2.1 相同点
(1)认证功能
(2)授权功能
(3)加密功能
(4)会话功能
(5)缓存支持
(6)remeberMe功能
2.2 不同点(Spring Security PK Apache Shiro)
优点:
(1)Spring Security基于Spring开发,项目中如果使用Spring作为基础,配合Spring Security做权限更加方便。而Shiro需要和Spring进行整合。
(2)Spring Security功能比Shiro更加丰富,例如安全防护方面。
(3)Spring Security社区资源相对比Shiro更加丰富。
(4)如果使用的是Spring Boot,Spring Cloud的话,三者可以无缝集成。
缺点:
(1)Shiro的配置和使用比较简单,Spring Security上手复杂些。
(2)Shiro依赖性低,不需要任何框架和容器,可以独立运行,而Spring Security依赖Spring容器。
企业里选择哪个安全框架,是因人而异,因团队而异,擅长哪个选择哪个,大部分的业务场景,两个框架都是可以满足需求的。
本节就到这里,下节我们通过编码对于Spring Security有一个基本的认识。
Spring Boot+Spring Security:初体验
说明
(1)JDK版本:1.8
(2)Spring Boot 2.0.6
(3)Spring Security 5.0.9
需求缘起
在上一篇文章中,我们对于Spring Security有了一个基本的了解,那么重点是在Spring Boot中如何使用Spring Security呢?
一、Spring Security初体验
这里我们通过简单的集成方式来对Spring Security有一个基本的认知。
1.1 创建项目
创建一个项目,取名为:springboot2-springSecurity01
1.2 添加依赖
在pom.xml文件中添加依赖,主要是springboot parent starter和-start-web以及spring security的依赖。
spring-boot-starter-parent依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
Spring Security和-web依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
1.3 创建Spring Boot启动类
使用@SpringBootApplication创建Spring Boot启动类,如下代码:
package com.kfit;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Springboot2SpringSecurity01Application {
public static void main(String[] args) {
SpringApplication.run(Springboot2SpringSecurity01Application.class, args);
}
}
如果使用的STS开发工具的话,上面的1.2和1.3都可以跳过。
1.4 编写Controller
随意编写一个测试Controller,这里取名为HelloController,如下示例代码:
package com.kfit.demo.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/hello")
public class HelloController {
@GetMapping
public String getWelcomeMsg() {
return "Hello,Spring Security";
}
}
1.5 启动测试
启动应用程序,然后进行测试:
(1)访问如下地址:
http://localhost:8080/hello
如果接口能正常访问,那么应该显示“Hello,SpringSecurity”,但是我们是没法正常访问的,出现了下图的身份验证输入框:
这是因为在SpringBoot中,默认的Spring Security就是生效了的,此时的接口都是被保护的,我们需要通过验证才能正常的访问。 Spring Security提供了一个默认的用户,用户名是user,而密码则是启动项目的时候自动生成的。
我们查看项目启动的日志,会发现如下的一段Log:
Using generated securitypassword: 74adcd57-f0be-46c3-87cc-6d9d712cbc27
我们直接用user和启动日志中的密码进行登录,登录成功后,就跳转到了接口正常调用的页面了。
二、小技巧
2.1 关闭验证功能
如果一开始不想使用验证功能,怎么关闭呢?
在配置文件中配置
security.basic.enabled=false
的方式,在5.x版本之后提示过时了,那么应该怎么办呢?
方式一:在启动类中排除
SecurityAutoConfiguration,
如下示例代码:
@SpringBootApplication(exclude=SecurityAutoConfiguration.class)
public class Springboot2SpringSecurity01Application {
public static void main(String[] args) {
SpringApplication.run(Springboot2SpringSecurity01Application.class, args);
}
}
另外在Spring Boot 2.x中下面配置项被废弃了:
security.basic.authorize-mode
security.basic.enabled
security.basic.path
security.basic.realm
security.enable-csrf
security.headers.cache
security.headers.content-security-policy
security.headers.content-security-policy-mode
security.headers.content-type
security.headers.frame
security.headers.hsts
security.headers.xss
security.ignored
security.require-ssl
security.sessions
2.2 自定义用户名和密码
上面的用户名是默认的,密码是随时生成的,实在是不方便,那么怎么自定义用户名和密码呢,只需要在配置文件application.properties文件中添加如下配置:
spring.security.user.name = admin
spring.security.user.password = 123456
Spring Boot+Spring Security:基于内存的认证信息
说明
(1)JDK版本:1.8
(2)Spring Boot 2.0.6
(3)Spring Security 5.0.9
需求缘起
上面我们简单体验了下Spring Security,但是现在只能有一个用户信息,我们这里希望可以配置多个账号信息,本节主要讲解下如何在内存中配置认证信息。
编码思路
我们要在内存中初始化我们的认证信息的话,那么需要是重写WebSecurityConfigurerAdapter类中的configure方法:
configure(AuthenticationManagerBuilder auth)
然后通过auth对象的inMemoryAuthentication()方法指定认证信息:
auth.inMemoryAuthentication().withUser("admin").password("123456");
一、基于内存的认证信息
我们基于上一篇文章中的项目springboot2-springSecurity01进行编码。
1.1 创建一个配置类
我们定义一个WebSecurityConfig,继承WebSecurityConfigurerAdapter,如下代码:
package com.kfit.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
/*
* 配置为从内存中进行加载认证信息.
* 这里配置了两个用户 admin和user
*/
auth.inMemoryAuthentication().withUser("admin").password("123456").roles();
auth.inMemoryAuthentication().withUser("user").password("123456").roles();
}
}
说明:
(1)@Configuration:注解这是一个配置类。
(2)@EnableWebSecurity:注解开启Spring Security的功能。
(3)WebSecurityConfigurerAdapter:继承WebSecurityConfigurerAdapter,并重写它的方法来设置一些web安全的细节。
(4)configure(AuthenticationManagerBuilder auth):在内存中创建了用户admin/user,密码为123456。
如果是5.x之前的版本的话,那么到这里启动的话,就可以正常访问了,但是如果是5.x的版本的话,可以正常启动,但是在登录页面输入admin/123456账号进行访问的话,会报如下错误:
java.lang.IllegalArgumentException:There is no PasswordEncoder mapped for the id "null"
这是因为Spring Security 5.0中新增了多种加密方式,也改变了密码的格式。
1.2 密码加密
上面异常原因就是因为没有指定加密方式,那么怎么指定呐?
1.2.1 方式一:通过AuthenticationManagerBuilder指定
在AuthenticationManagerBuilder的方法中就可以指定加密方式了,如下代码:
auth.inMemoryAuthentication()
.passwordEncoder(new BCryptPasswordEncoder())
.withUser("admin")
.password(new BCryptPasswordEncoder().encode("123456"))
.roles();
auth.inMemoryAuthentication()
.passwordEncoder(new BCryptPasswordEncoder())
.withUser("user")
.password(new BCryptPasswordEncoder().encode("123456"))
.roles();
说明:
(1)Bcrypt: bcrypt是一种跨平台的文件加密工具。bcrypt 使用的是布鲁斯·施内尔在1993年发布的 Blowfish 加密算法。由它加密的文件可在所有支持的操作系统和处理器上进行转移。它的口令必须是8至56个字符,并将在内部被转化为448位的密钥。
1.2.2 方式二:通过@Bean注入指定PasswordEncoder
在WebSecurityConfig配置类中,通过@Bean注入PasswordEncoder具体的实现类,如下代码:
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
加入这个之后,需要修改configure的密码加密:
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
/*
* 配置为从内存中进行加载认证信息.
* 这里配置了两个用户 admin和user
*/
auth.inMemoryAuthentication()
.withUser("admin")
.password(passwordEncoder().encode("123456"))
.roles();
auth.inMemoryAuthentication()
.withUser("user")
.password(passwordEncoder().encode("123456"))
.roles();
}
对于上面的两种方式,推荐使用第二种方式,这种方式一方面更容易理解,另外代码扩展性更强。
Spring Boot+Spring Security:基于内存的角色授权
说明
(1)JDK版本:1.8
(2)Spring Boot 2.0.6
(3)Spring Security 5.0.9
需求缘起
之前我们基于内存的方式,构建了两个账号admin和user,对于这两个账号在实际项目中会有不同的角色,比如管理员角色和普通用户角色,对于不同的角色,那么允许访问的方法会不一样。
编码思路
对于不同角色具有不同方法的权限的问题,主要需要思考几个点:
(1)如何给指定的用户指定角色
通过AuthenticationManagerBuilder的roles()方法,就可以指定角色,示例代码:
auth.inMemoryAuthentication()
.withUser("admin")
.password(passwordEncoder().encode("123456"))
.roles("beijingAdmin","shanghaiAdmin");
上面的示例中指定了用户admin,具有beijingAdmin,shanghaiAdmin的角色。
(2)如何开启方法级别安全控制
想要开启Spring方法级安全,你需要在已经添加了@Configuration注解的类上再添加@EnableGlobalMethodSecurity注解即可。
(3)如何配置方法级别的权限控制
使用注解@PreAuthorize("hasAnyRole('admin')")即可指定访问级别的角色。
一、基于内存的角色授权
这里基于上一篇文章进行编码。
1.1 为用户配置角色
通过AuthenticationManagerBuilder的roles()方法分配角色:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
/*
* 配置为从内存中进行加载认证信息.
* 这里配置了两个用户 admin和user
*/
auth.inMemoryAuthentication()
.withUser("admin")
.password(passwordEncoder().encode("123456"))
.roles("admin");
auth.inMemoryAuthentication()
.withUser("user")
.password(passwordEncoder().encode("123456"))
.roles("normal");
}
1.2 开启Spring方法级安全
想要开启Spring方法级安全,你需要在已经添加了@Configuration注解的类上再添加@EnableGlobalMethodSecurity注解
(1)prePostEnabled :决定Spring Security的前注解是否可用 [@PreAuthorize,@PostAuthorize,..]
(2)secureEnabled : 决定是否Spring Security的保障注解 [@Secured] 是否可用。
(3)jsr250Enabled :决定 JSR-250 annotations 注解[@RolesAllowed..] 是否可用。
我们这里在WebSecurityConfig进行添加配置:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
}
说明:只有添加了@EnableGlobalMethodSecurity(prePostEnabled=true)之后,@PreAuthorize("hasAnyRole('admin')")才能生效。
1.3 配置方法级拥有的角色
我们在HelloController添加两个方法,使用注解@PreAuthorize指定访问该方法需要的角色:
@RestController
@RequestMapping("/hello")
public class HelloController {
@GetMapping
public String getWelcomeMsg() {
return "Hello,Spring Security";
}
@GetMapping("/helloAdmin")
@PreAuthorize("hasAnyRole('admin')")
public String helloAdmin() {
return "Hello,admin";
}
@GetMapping("/helloUser")
@PreAuthorize("hasAnyRole('admin','normal')")
public String helloUser() {
return "Hello,user";
}
}
1.4 测试角色
到这里就可以启动测试下:
(1)启动应用程序,访问如下地址:
http://127.0.0.1:8080/hello/helloUser
跳转到登录页面,输入账号user/123456,成功登录之后,会看到返回信息:Hello,user
然后在输入另外一个地址:
http://127.0.0.1:8080/hello/helloAdmin
这时候会看到403的报错:
(2)重新启动应用程序,输入admin/123456账号进行测试,都能正常访问。
Spring Boot+Spring Security:基于内存数据库的身份认证和角色授权
说明
(1)JDK版本:1.8
(2)Spring Boot 2.0.6
(3)Spring Security 5.0.9
(4)Spring Data JPA 2.0.11.RELEASE
(5)hibernate5.2.17.Final
(6)hsqldb2.4.1
需求缘起
在前面我们使用基于内存的方式体验了下Spring Security,在实际项目中,都是需要数据库进行操作的,本节使用hsqldb内存数据库进行说明。
编码思路
这里我们使用的是Spring Data JPA进行操作数据库,所以需要添加相关的依赖;其次就是需要定义一个保存用户基本的实体类;再者需要定义相应的服务获取用户的信息;最后重写UserDetailsService的loadUserByUsername方法从数据库中获取用户信息,传给Spring Security进行处理。
一、基于内存数据库的身份认证和角色授权
1.1 添加依赖
在pom.xml文件中添加SpringData JPA和hsqldb的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<scope>runtime</scope>
</dependency>
1.2 创建实体类
创建UserInfo实体类:
package com.kfit.permission.bean;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class UserInfo {
public enum Role{
admin,normal
}
@Id @GeneratedValue
private long uid;//主键.
private String username;//用户名.
private String password;//密码.
@Enumerated(EnumType.STRING)
private Role role;
public long getUid() {
return uid;
}
public void setUid(long uid) {
this.uid = uid;
}
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 Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
}
1.3 创建Repository
创建和数据库交互的UserInfoRepository:
package com.kfit.permission.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.kfit.permission.bean.UserInfo;
public interface UserInfoRepository extends JpaRepository<UserInfo,Long> {
public UserInfo findByUsername(String username);
}
1.4 创建Service
创建UserInfoService:
package com.kfit.permission.service;
import com.kfit.permission.bean.UserInfo;
public interface UserInfoService {
public UserInfo findByUsername(String username);
}
创建UserInfoService的实现类:
package com.kfit.permission.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.kfit.permission.bean.UserInfo;
import com.kfit.permission.repository.UserInfoRepository;
import com.kfit.permission.service.UserInfoService;
@Service
public class UserInfoServiceImpl implements UserInfoService {
@Autowired
private UserInfoRepository userInfoRepository;
@Override
public UserInfo findByUsername(String username) {
return userInfoRepository.findByUsername(username);
}
}
1.5 自定义UserDetailsService
自定义一个UserDetailsService,取名为CustomUserDetailService,该类需要实现接口UserDetailsService,主要是实现loadUserByUsername方法:
package com.kfit.config;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import com.kfit.permission.bean.UserInfo;
import com.kfit.permission.service.UserInfoService;
@Component
public class CustomUserDetailService implements UserDetailsService{
@Autowired
private UserInfoService userInfoService;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("CustomUserDetailService.loadUserByUsername()");
//通过username获取用户信息
UserInfo userInfo = userInfoService.findByUsername(username);
if(userInfo == null) {
throw new UsernameNotFoundException("not found");
}
//定义权限列表.
List<GrantedAuthority> authorities = new ArrayList<>();
// 用户可以访问的资源名称(或者说用户所拥有的权限) 注意:必须"ROLE_"开头
authorities.add(new SimpleGrantedAuthority("ROLE_"+userInfo.getRole().name()));
User userDetails = new User(userInfo.getUsername(),passwordEncoder.encode(userInfo.getPassword()),authorities);
return userDetails;
}
}
说明:
(1) 通过username获取用户的信息。
(2) 定义一个User(实现了接口UserDetails)对象,返回用户的username,passowrd和权限列表。
(3) 需要注意,定义角色集的时候,需要添加前缀“ROLE_”。
(4) 这里的密码需要使用PasswordEncoder进行加密,否则会报“无效的凭证”。
1.6 初始化测试账号
这里我们使用一个DataInit类,初始化两个账号admin/123和user/123:
package com.kfit.permission.init;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.kfit.permission.bean.UserInfo;
import com.kfit.permission.bean.UserInfo.Role;
import com.kfit.permission.repository.UserInfoRepository;
@Service
public class DataInit {
@Autowired UserInfoRepository userInfoRepository;
@PostConstruct
public void dataInit() {
UserInfo admin = new UserInfo();
admin.setUsername("admin");
admin.setPassword("123");
admin.setRole(Role.admin);
userInfoRepository.save(admin);
UserInfo user = new UserInfo();
user.setUsername("user");
user.setPassword("123");
user.setRole(Role.normal);
userInfoRepository.save(user);
}
}
1.7 启动测试
(1)测试账号:user/123
启动应用访问地址:
http://127.0.0.1:8080/hello/helloUser
自动跳转到登录页面,输入账号user/123,可以看到页面:
紧接着访问地址:
http://127.0.0.1:8080/hello/helloAdmin
访问被拒绝:
(2)测试账号:admin/123
现在没有退出按钮,只能重新启动应用程序,然后输入账号admin/123,上面的两个地址应该都是可以正常访问的。
Spring Boot+Spring Security:基于MySQL数据库的身份认证和角色授权
说明
(1)JDK版本:1.8
(2)Spring Boot 2.0.6
(3)Spring Security 5.0.9
(4)Spring Data JPA 2.0.11.RELEASE
(5)hibernate5.2.17.Final
(6)MySQLDriver 5.1.47
(7)MySQL 8.0.12
需求缘起
在前面使用的是内存数据库,本节使用MySQL数据库进行操作。
编码思路
在《基于内存数据库的身份认证和角色授权》的代码转换为MySQL数据库的存储方式的话,非常的简单,只需要添加MySQL数据库的驱动包以及配置好数据源和JPA。
一、基于MySQL数据库的身份认证和角色授权
1.1 添加依赖
在pom.xml文件中去掉hsqldb的依赖,然后添加mysql的依赖:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
1.2 添加配置信息
在application.properties文件中添加配置信息:
spring.datasource.url = jdbc:mysql://localhost:3306/spring-security
spring.datasource.username = root
spring.datasource.password = root
spring.datasource.driverClassName = com.mysql.jdbc.Driver
# Specify the DBMS
spring.jpa.database = MYSQL
# Show or not log for each sql query
spring.jpa.show-sql = true
# Hibernate ddl auto (create, create-drop, update)
spring.jpa.hibernate.ddl-auto = create-drop
1.3 启动测试
启动应用程序,成功的话可以看到数据库的数据:
按照之前的流程测试下,结果是一样的。到这里的话,我并没有做过多的编码,那么这么轻松从内存数据库转换到MySQL数据库,这就是框架给我们提供的便利。
二、数据库密码加密保存
到这里基本的流程都是没有问题的,但是我们发现数据库的密码都是明文显示的,这个就要命了,那么数据库的密码怎么加密保存呢?其实也很简单,在初始化用户信息的时候,就进行加密就可以了,具体的操作如下:
2.1 修改DataInit
修改DataInit类,注入PasswordEncoder,使用PasswordEncoder的encode方法对密码进行加密:
package com.kfit.permission.init;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import com.kfit.permission.bean.UserInfo;
import com.kfit.permission.bean.UserInfo.Role;
import com.kfit.permission.repository.UserInfoRepository;
@Service
public class DataInit {
@Autowired
private UserInfoRepository userInfoRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@PostConstruct
public void dataInit() {
UserInfo admin = new UserInfo();
admin.setUsername("admin");
admin.setPassword(passwordEncoder.encode("123"));
admin.setRole(Role.admin);
userInfoRepository.save(admin);
UserInfo user = new UserInfo();
user.setUsername("user");
user.setPassword(passwordEncoder.encode("123"));
user.setRole(Role.normal);
userInfoRepository.save(user);
}
}
2.2 修改CustomUserDetailService
在添加用户的时候,已经加密了,那么在loadUserByUsername方法中返回的UserDetails就不需要再加密了,修改为如下:
package com.kfit.config;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
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.Component;
import com.kfit.permission.bean.UserInfo;
import com.kfit.permission.service.UserInfoService;
@Component
public class CustomUserDetailService implements UserDetailsService{
@Autowired
private UserInfoService userInfoService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("CustomUserDetailService.loadUserByUsername()");
//通过username获取用户信息
UserInfo userInfo = userInfoService.findByUsername(username);
if(userInfo == null) {
throw new UsernameNotFoundException("not found");
}
//定义权限列表.
List<GrantedAuthority> authorities = new ArrayList<>();
// 用户可以访问的资源名称(或者说用户所拥有的权限) 注意:必须"ROLE_"开头
authorities.add(new SimpleGrantedAuthority("ROLE_"+userInfo.getRole().name()));
User userDetails = new User(userInfo.getUsername(),userInfo.getPassword(),authorities);
return userDetails;
}
}
2.3 启动测试
启动应用,查看数据库的用户信息:
此时看到的密码已经是加密的了,访问下如下的地址:http://127.0.0.1:8080/hello/helloUser输入账号user/123看是否可以正常登录吧。
Spring Boot+Spring Security:自定义登录页面和构建主页
说明
(1)JDK版本:1.8
(2)Spring Boot 2.0.6
(3)Spring Security 5.0.9
(4)Spring Data JPA 2.0.11.RELEASE
(5)hibernate5.2.17.Final
(6)MySQLDriver 5.1.47
(7)MySQL 8.0.12
需求缘起
在上一节我们已经能够把用户信息持久化到数据库了,十万里长征也完成了一大半了,黎明的曙光就要到来了。本节说明下如何自定义登录、主界面等页面。
一、准备工作
1.1 添加模板引擎
这里使用了thymeleaf模板引擎,在pom.xml进行添加:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
1.2 配置Spring Security的登录页面路径
在WebSecurityConfig复写configure(HttpSecurityhttp)方法,复写登录页面的路径,如下示例代码:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() // 定义哪些URL需要被保护、哪些不需要被保护
.antMatchers("/login").permitAll()// 设置所有人都可以访问登录页面
.anyRequest().authenticated() // 任何请求,登录后可以访问
.and()
.formLogin().loginPage("/login")
;
}
二、自定义登录界面
2.1 编写登录页面的html文件
在templates下编写login.html文件,内容如下:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Login </title>
</head>
<body>
<div th:if="${param.error}">
用户名密码错误,要不去<a th:href="@{/}">首页</a>看看?
</div>
<div th:if="${param.logout}">
您已经登出
</div>
<form th:action="@{/login}" method="post">
<div><label> 用户名 : <input type="text" name="username"/> </label></div>
<div><label> 密码: <input type="password" name="password"/> </label></div>
<div><input type="submit" value="登录"/></div>
</form>
</body>
</html>
2.2 编写登录页面请求映射
创建一个HomeController类,添加login方法,使用@GetMapping进行请求映射,如下示例代码:
package com.kfit.permission.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HomeController {
@GetMapping("/login")
public String login() {
return "/login";
}
}
2.3 启动测试
启动应用程序,访问到的登录页面已经是我们自己编写的页面了:
三、构建主页
3.1 编写主页的html文件
编写登录页面index.html,如下示例代码:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security入门</title>
</head>
<body>
<h1>欢迎使用Spring Security!</h1>
<p>点击 <a th:href="@{/hello}">这里</a> 打个招呼吧</p>
<p> <a th:href="@{/hello/helloAdmin}">admin page</a></p>
<p><a th:href="@{/hello/helloUser}">user page</a></p>
</body>
</html>
3.2 编写主页页面请求映射
在HomeController中添加index映射方法:
@GetMapping({"","/","/index"})
public String index() {
return "/index";
}
3.3 测试
启动登录可以看到如下页面: