概述
- 认证(你是谁,户/设备/系统
- 验证(你能干什么,也叫权限控制/授权,允许执行的操作)
- 基于Filter , Servlet, AOP实现身份认证和权限验证
实例驱动学习
入门案例
1、新建工程,导入依赖
<!--spring-security相关依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2、配置访问地址,启动测试,获得秘钥
登陆名:user
密码:查看日志
3、小结
使用AOP做了拦截,拦截后再访问的servlet
security相关配置
自定义用户名和密码
在Application.yml 中配置文件上的security的user和password
spring:
security:
user:
name: admin
password: admin
关闭验证
在启动类的注解中,排除安全验证,使用内存中的用户信息
使用内存中的账户
使用类WebSecurityConfigurerAdapter控制安全管理内容
1. 自定义类继承WebSecurityConfigurerAdapter,声明是个配置类、开启WebSecurity
2. 重写configure方法
3. 在SpringSecurity_v5.0版本中,密码必须使用一个加密方式
4. 构建一个方法创建一个BCrypt加密类,用于加密操作(BCrypt跨平台)
@Configuration
@EnableWebSecurity
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 得到一个加密类,
PasswordEncoder passwordEncoder = passwordEncoder();
auth.inMemoryAuthentication().withUser("zhangsan").password(passwordEncoder.encode("zhangsan")).roles();
auth.inMemoryAuthentication().withUser("lisi").password(passwordEncoder.encode("lisi")).roles();
auth.inMemoryAuthentication().withUser("admin").password(passwordEncoder.encode("admin")).roles();
}
// 构建一个方法创建一个加密类,放入容器中
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
使用数据库的账户
1、导入依赖
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<!--spring-jpa依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>2.4.5</version>
</dependency>
2、封装对象,创建一个实体类,初始化数据 使用JPA连接数据库
3.1、创建一个实体类
package com.study.entity;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
@Data
public class UserInfo {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String username;
private String password;
private String role;
}
3.2、创建一个dao
package com.study.dao;
import com.study.entity.UserInfo;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserInfoDao extends JpaRepository<UserInfo, Long> {
UserInfo findByUsername(String name);
}
3.3、创建service层的类 接口和实现类
package com.study.service;
import com.study.entity.UserInfo;
public interface UserInfoService {
UserInfo findUserInfo(String username);
}
package com.study.service.impl;
import com.study.dao.UserInfoDao;
import com.study.entity.UserInfo;
import com.study.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserInfoServiceImpl implements UserInfoService {
@Autowired
UserInfoDao userInfoDao;
public UserInfo findUserInfo(String username) {
UserInfo userInfo = userInfoDao.findByUsername(username);
return userInfo;
}
}
3.4、配置数据库连接信息
spring.datasource.url=jdbc:mysql://8.129.121.241:3306/springsecurity?serverTimezone=GMT%2B8&characterEncoding=utf8
spring.datasource.username=xxyy
spring.datasource.password=xxYY11..
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#spring-data-jpa
spring.jpa.generate-ddl=true
spring.jpa.show-sql=true
spring.jpa.database=mysql
3.5、初始化数据库数据
package com.study.init;
import com.study.dao.UserInfoDao;
import com.study.entity.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
public class JdbcInit {
@Autowired
private UserInfoDao userInfoDao;
@PostConstruct
public void init() {
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
UserInfo userInfo = new UserInfo();
userInfo.setUsername("lisi");
userInfo.setPassword(passwordEncoder.encode("lisi"));
userInfo.setRole("normal");
userInfoDao.save(userInfo);
}
}
3.6、查询数据库,构造一个User对象,用于框架中使用
package com.study.provider;
import com.study.dao.UserInfoDao;
import com.study.entity.UserInfo;
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.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class MyUserDetailService implements UserDetailsService {
@Autowired
private UserInfoDao userInfoDao;
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user=null;
UserInfo userInfo=null;
if(username!=null){
userInfo = userInfoDao.findByUsername(username);
if (userInfo!=null){
List<GrantedAuthority> list=new ArrayList<GrantedAuthority>();
GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_"+userInfo.getRole());
list.add(authority);
user=new User(userInfo.getUsername(),userInfo.getPassword(),list);
}
}
return user;
}
}
4、编写配置类,将通过数据库得到的User对象,进行角色配置
package com.study.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
}
5、添加Controller,最后测试验证,注意将添加数据库信息的注解注释掉
package com.study.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping(value = "/hello")
public String hello() {
return "hello spring-security";
}
@RequestMapping(value = "/hellouser")
@PreAuthorize(value = "hasAnyRole('ROLE_admin','ROLE_normal')")
public String helloUser() {
return "hello spring-security have normail Admin role";
}
@RequestMapping(value = "/helloadmin")
@PreAuthorize(value = "hasAnyRole('ROLE_admin')")
public String helloAdmin() {
return "hello spring-security have Admin role";
}
}
踩坑
所有的权限都要加上 “ROLE_” 作为前缀
基于角色权限
认证和授权
-
authentication:认证,认证访问者是谁。一 个用户或者一个其他系统是不是当前要访问的系统中的有效用户。
-
authorization:授权,访问者能做什么?
比如说张三用户要访问一个公司OA系统。首先系统要判断张三 是不是公司中的有效用户 -
例如:认证:张三是不是有效的用户,是不是公司的职员
授权:判断张三能否做某些操作,如果张三是个领导可以批准下级的请假,其他的操作
RBAC是什么?
-
权限:能对资源的操作,比如增加, 修改,删除,查看等等
-
角色:自定义的,表示权限的集合。一个角色可以有多个权限
-
用户:一个用户赋予某种权限,就等同于拥有这些权限
RBAC设计中的表
-
用户表:用户认证(登录用到的表)
用户名,密码,是否启用,是否锁定等信息 -
角色表:定义角色信息
角色名称,角色的描述 -
用户和角色的关系表:用户和角色是多对多的关系
一个用户可以有多个角色,一个角色可以有多个用户 -
权限表,角色和权限的关系表
角色可以有哪些权限
spring security中认证的接口和类
UserDetails 接口
表示用户信息
// 接口,表示用户信息
public interface UserDetails extends Serializable {
// 权限集合
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
// 账号是否过期
boolean isAccountNonExpired();
// 账号是否锁定
boolean isAccountNonLocked();
// 证书是否过期
boolean isCredentialsNonExpired();
// 账号是否启用
boolean isEnabled();
}
UserDetails实现类
package org.springframework.security.core.userdetails.User;
可以自定义类实现UserDetails接口,作为你的系统中的用户类。这个类可以交给spring security使用
UserDetailsService接口
主要作用:
获取用户信息,得到是UserDetails对象。一般项目中都需要自定义类实现这个接口,从数据库中获取数据
只有一个方法需要实现:
UserDetails loadUserByUsername(String var1) : 根据用户名称,获取用户信息(用户名称,密码,角色结合,是否可用,是否锁定等信息)
UserDetailsService接口的实现类类:
1、InMemoryUserDetailsManager :在管理内存中用户信息
优点:使用方便
缺点:数据不是持久的,系统重启后数据恢复原样
2、JdbcUserDetailsManager:用户信息存放在数据库中,底层使用jdbcTemplate操作数据库。
可以 使用JdbcUserDetailsManager中的方法完成用户的管理
createUser:创建用户
updateUser:更新用户
deleteUser:删除用户
UserExists:判断用户是否存在
数据库文件:
package org.springframework.security.core.userdetails.jdbc;
- users.ddl 文件
create table users(username varchar_ignorecase(50) not null primary key,password varchar_ignorecase(500) not null,enabled boolean not null);
create table authorities (username varchar_ignorecase(50) not null,authority varchar_ignorecase(50) not null,constraint fk_authorities_users foreign key(username) references users(username));
create unique index ix_auth_username on authorities (username,authority);
注:建表时若使用的是MySQL,将varchar_ignorecase替换为varchar
使用案例-1
- 在管理内存中用户信息
1、创建一个项目
2、加入maven坐标
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.4.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.4.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.4.5</version>
</dependency>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
3、创建应用的配置类
@Configuration
public class ApplicationConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService userDetailsService() {
PasswordEncoder encoder = passwordEncoder();
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("admin")
.password(encoder.encode("admin"))
.roles("ADMIN", "USER").build());
manager.createUser(User.withUsername("zs")
.password(encoder.encode("zs"))
.roles("USER").build());
return manager;
}
}
4、创建类继承WebSecuri tyConfi gurerAdapter
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService detailsService=null;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.userDetailsService(detailsService);
}
}
5、测试
使用案例-2
- 使用访问数据库,获取认证的用户信息
1、创建项目
2、导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.4.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.4.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.4.5</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>2.4.5</version>
</dependency>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
3、创建应用的配置类,创建JdbcUserDetatilsService对象
@Configuration
public class ApplicationConfig {
//通过spring容器注入 DataSource
@Autowired
private DataSource dataSource;
//创建PasswordEncoder对象
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
//创建JdbcUserDetatilsService对象
@Bean(name = "jdbcUserDetatilsService")
public UserDetailsService jdbcUserDetatilsService() {
System.out.println("===dataSource===" + dataSource);
PasswordEncoder encoder = passwordEncoder();
//初始数据源DataSource --- JdbcTemplate对象
JdbcUserDetailsManager manager = new JdbcUserDetailsManager(dataSource);
//如果数据库中已经存在账号不添加
if (!manager.userExists("admin")) {
manager.createUser(User.withUsername("admin")
.password(encoder.encode("admin"))
.roles("ADMIN", "USER", "MANAGER").build());
}
if (!manager.userExists("zs")) {
manager.createUser(User.withUsername("zs").
password(encoder.encode("zs"))
.roles("USER").build());
}
if (!manager.userExists("ls")) {
manager.createUser(User.withUsername("ls")
.password(encoder.encode("ls"))
.roles("USER", "NORMAL").build());
}
return manager;
}
}
4、创建一个security的配置,自定义安全配置信息。指定JdbcUserDetatilsService类
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("jdbcUserDetatilsService")
private UserDetailsService userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.userDetailsService(userDetailsService);
}
}
5、修改application.properties文件 连接数据库。配置数据源DataSource
spring.datasource.url=jdbc:mysql://8.129.121.241:3306/t-ssm?serverTimezone=GMT%2B8&characterEncoding=utf8
spring.datasource.username=xxyy
spring.datasource.password=xxYY11..
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
6.测试
使用案例-3
- 自定义角色信息(定义”用户、角色、关系“三张表)
1、新建maven项目
2、导入坐标
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<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>
<!--mybatis启动依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
3、编写application.properties
spring.datasource.url=jdbc:mysql://8.129.*.*:3306/t-ssm?serverTimezone=GMT%2B8&characterEncoding=utf8
spring.datasource.username=******
spring.datasource.password=******
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#扫描的mapper.xml
mybatis.mapper-locations=classpath:/mapper/*Mapper.xml
#对实体类起别名(简写)
mybatis.type-aliases-package=com.wkcto.entity
#mybatis打印log日志
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
4、Controller、Service、DAO三层
4.1、实体类
- SysUser类
public class SysUser implements UserDetails {
private Integer id;
private String username;
private String password;
private String realname;
private boolean isExpired;
private boolean isLocked;
private boolean isCredentials;
private boolean isEnabled;
private Date createTime;
private Date loginTime;
private List<GrantedAuthority> authorities;
// 省略“有参 无参 get set toString”方法
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return isExpired;
}
@Override
public boolean isAccountNonLocked() {
return isLocked;
}
@Override
public boolean isCredentialsNonExpired() {
return isExpired;
}
@Override
public boolean isEnabled() {
return isEnabled;
}
}
- SysRole类
public class SysRole {
private Integer id;
private String name;
private String memo;
// 省略“有参 无参 get set toString”方法
}
4.2、Dao层
- SysUserMapper类
@Repository
public interface SysUserMapper {
int insertSysUser(SysUser user);
SysUser selectSysUserByUser(String username);
}
- SysUserMapper.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.wkcto.mapper.RoleMapper">
<select id="selectRoleByUser" resultMap="roleMapper">
select r.id, r.rolename,r.rolememo from sys_user_role ur , sys_role r
where ur.roleid = r.id and ur.userid=#{userid}
</select>
<resultMap id="roleMapper" type="com.wkcto.entity.SysRole">
<id column="id" property="id"/>
<result column="rolename" property="name"/>
<result column="rolememo" property="memo"/>
</resultMap>
</mapper>
- RoleMapper类
@Repository
public interface RoleMapper {
List<SysRole> selectRoleByUser(Integer userId);
}
- RoleMapper.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.wkcto.mapper.RoleMapper">
<select id="selectRoleByUser" resultMap="roleMapper">
select r.id, r.rolename,r.rolememo from sys_user_role ur , sys_role r
where ur.roleid = r.id and ur.userid=#{userid}
</select>
<resultMap id="roleMapper" type="com.wkcto.entity.SysRole">
<id column="id" property="id"/>
<result column="rolename" property="name"/>
<result column="rolememo" property="memo"/>
</resultMap>
</mapper>
4.3、Service层
-
创建自定义的UserDetatilsService实现类
根据username从数据库中查询账户信息SysUser对象,再根据user的id,查询Sys_Role表获取Role信息
@Service
public class JdbcUserDetatilsService implements UserDetailsService {
@Autowired
private SysUserMapper userMapper;
@Autowired
private RoleMapper roleMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Logs.msg("loadUserByUsername", username);
SysUser user = userMapper.selectSysUserByUser(username);
Logs.msg("selectSysUserByUser", user);
if (user != null) {
List<SysRole> roleList = roleMapper.selectRoleByUser(user.getId());
List<GrantedAuthority> authorities = new ArrayList<>();
for (SysRole role : roleList) {
String roleName = role.getName();
GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_" + roleName);
authorities.add(authority);
}
user.setAuthorities(authorities);
}
Logs.msg("authorities", user);
return user;
}
}
4.4、Controller层
- IndexController:转发到首页index.html
- MyController:连接指定的Controller
- IndexController
@Controller
public class IndexController {
@GetMapping("/index")
public String toIndexHtml(){
return "forward:/index.html";
}
}
- MyController
@RestController
public class MyController {
@GetMapping(value = "/access/user",produces = "text/html;charset=utf-8")
public String sayUser(){
return "zs is user role";
}
@GetMapping(value = "/access/read",produces = "text/html;charset=utf-8")
public String sayRead(){
return "ls is read";
}
@GetMapping(value = "/access/admin",produces = "text/html;charset=utf-8")
public String sayAdmin(){
return "admin is user,admin role";
}
}
5、创建类继承WebSecurityConfigurerAdapter,自定义安全的配置
@Configuration
@EnableWebSecurity
public class CustomSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/index").permitAll()
.antMatchers("/access/user/**").hasRole("USER")
.antMatchers("/access/read/**").hasRole("READ")
.antMatchers("/access/admin/**").hasRole("ADMIN")
.and()
.formLogin();
}
}
6、主类
@MapperScan(value = "com.wkcto.mapper")
@SpringBootApplication
public class UserRoleApplication {
@Autowired
private SysUserMapper userMapper;
public static void main(String[] args) {
SpringApplication.run(UserRoleApplication.class, args);
}
// @PostConstruct //在servlet加载之前执行,且只执行一次
public void jdbcInit() {
PasswordEncoder encoder = new BCryptPasswordEncoder();
List<GrantedAuthority> list = new ArrayList();
Date curDate = new Date();
GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_" + "USER");
list.add(authority);
SysUser user = new SysUser("zs", encoder.encode("zs"), "张三", true, true, true, true, curDate, curDate, list);
userMapper.insertSysUser(user);
System.out.println(user);
}
// @PostConstruct
public void jdbcInit2() {
Date curDate = new Date();
PasswordEncoder encoder = new BCryptPasswordEncoder();
List<GrantedAuthority> list = new ArrayList<>();
//参数角色名称,需要以"ROLE_"开头, 后面加上自定义的角色名称
GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_" + "READ");
list.add(authority);
SysUser user = new SysUser(
"lisi", encoder.encode("ls"), "李四", true, true, true, true, curDate, curDate, list
);
userMapper.insertSysUser(user);
List<GrantedAuthority> list2 = new ArrayList<>();
GrantedAuthority authority2 = new SimpleGrantedAuthority("ROLE_" + "AMDIN");
GrantedAuthority authority3 = new SimpleGrantedAuthority("ROLE_" + "USER");
list.add(authority2);
list.add(authority3);
SysUser user2 = new SysUser(
"admin", encoder.encode("admin"), "管理员", true, true, true, true, curDate, curDate, list2
);
userMapper.insertSysUser(user2);
}
}
7、index.html 首页
- src/main/resources/static/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>验证访问</p>
<a href="/access/user">验证zs</a> <br/>
<a href="/access/read">验证ls</a> <br/>
<a href="/access/admin">验证admin</a> <br/>
<a href="/logout">退出系统</a>
</body>
</html>
8、Sql脚本文件
/*
MySQL Backup
Database: t-ssm
Backup Time: 2021-05-25 16:33:01
*/
SET FOREIGN_KEY_CHECKS=0;
DROP TABLE IF EXISTS `t-ssm`.`sys_role`;
DROP TABLE IF EXISTS `t-ssm`.`sys_user`;
DROP TABLE IF EXISTS `t-ssm`.`sys_user_role`;
CREATE TABLE `sys_role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`rolename` varchar(255) DEFAULT NULL,
`rolememo` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
CREATE TABLE `sys_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(100) DEFAULT NULL,
`password` varchar(100) DEFAULT NULL,
`realname` varchar(200) DEFAULT NULL,
`isenable` int(11) DEFAULT NULL,
`islock` int(11) DEFAULT NULL,
`isexpire` int(11) DEFAULT NULL,
`iscredentials` int(11) DEFAULT NULL,
`createtime` date DEFAULT NULL,
`logintime` date DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
CREATE TABLE `sys_user_role` (
`userid` int(11) DEFAULT NULL,
`roleid` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
BEGIN;
LOCK TABLES `t-ssm`.`sys_role` WRITE;
DELETE FROM `t-ssm`.`sys_role`;
INSERT INTO `t-ssm`.`sys_role` (`id`,`rolename`,`rolememo`) VALUES (1, 'USER', '普通用户'),(2, 'ADMIN', '管理员'),(3, 'READ', '只读');
UNLOCK TABLES;
COMMIT;
BEGIN;
LOCK TABLES `t-ssm`.`sys_user` WRITE;
DELETE FROM `t-ssm`.`sys_user`;
INSERT INTO `t-ssm`.`sys_user` (`id`,`username`,`password`,`realname`,`isenable`,`islock`,`isexpire`,`iscredentials`,`createtime`,`logintime`) VALUES (1, 'zs', '$2a$10$iEZgNdcXVAfNKfaa/zlD8un7nqW5mASwqPQAFLpluTfpxIN8lamFG', '张三', 1, 1, 1, 1, '2021-05-25', '2021-05-25'),(2, 'ls', '$2a$10$SA8yZU5D3/z9k/RV1SeswORJ6DKj8och26E7E2wInl5IkHvss1oc6', '李四', 1, 1, 1, 1, '2021-05-25', '2021-05-25'),(3, 'admin', '$2a$10$B8zilw4lDibcN8Za8EA3OOsllMwKcLZMPznjQwgyP4BYMPgK9I7ni', '管理员', 1, 1, 1, 1, '2021-05-25', '2021-05-25');
UNLOCK TABLES;
COMMIT;
BEGIN;
LOCK TABLES `t-ssm`.`sys_user_role` WRITE;
DELETE FROM `t-ssm`.`sys_user_role`;
INSERT INTO `t-ssm`.`sys_user_role` (`userid`,`roleid`) VALUES (1, 1),(2, 3),(3, 1),(3, 2);
UNLOCK TABLES;
COMMIT;
自定义登陆界面
步骤
- 创建个自定义的登陆界面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>验证访问</p>
<a href="/access/user">验证zs</a> <br/>
<a href="/access/read">验证ls</a> <br/>
<a href="/access/admin">验证admin</a> <br/>
<a href="/logout">退出系统</a>
</body>
</html>
- 修改继承 WebSecurityConfigurerAdapter 类的配置
@Configuration
@EnableWebSecurity
public class CustomSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/index","/mylogin.html","/login","/error.html").permitAll()//将要用到的地址排除
.antMatchers("/access/user/**").hasRole("USER")
.antMatchers("/access/read/**").hasRole("READ")
.antMatchers("/access/admin/**").hasRole("ADMIN")
.and()
.formLogin()
.loginPage("/mylogin.html")//登陆的自定义页面
.failureUrl("/error.html")//配置错误登陆页面
.loginProcessingUrl("/login")//登陆时提交的地址
.and()
.csrf().disable();//将跨域问题禁用
}
}
ajax 登陆页面
-
引入 jQuery.js 文件,编写ajax登陆界面代码,白名单添加引入的 js 文件 url
1)在 static 目录中,创建js目录,拷贝 jquery-3.4.1.js 文件
2)编写 myajax.html 文件,并在文件中引入jquery
3)加入 ajax 请求处理,并定义 DOM 对象
<head>
<meta charset="UTF-8">
<title>myajax</title>
<!--引入jquery-->
<script type="text/javascript" src="/js/jquery-3.4.1.js"></script>
<script type="text/javascript">
$(function () {
$("#btnLogin").click(function () {
alert("click");
var uname=$("#username").val();
var upwd=$("#password").val();
$.ajax({
url:"/login",
type:"POST",
data:{
"username":uname,
"password":upwd
},
dataType:"json",
success:function (resp) {
alert(resp.msg)
}
})
})
})
</script>
</head>
<body>
<h3>自定义Ajax登陆界面</h3>
<div> <!--定义的DOM对象-->
用户名:<input type="text" id="username" value=""><br>
密 码:<input type="text" id="password" value=""><br>
<button id="btnLogin">使用ajax登陆</button>
</div>
</body>
- 创建 Handler 实现两个不同接口
- MyFailureHandler
@Component("myFailureHandler")
public class MyFailureHandler implements AuthenticationFailureHandler {
/*
参数:
request : 请求对象
response:应答对象
authentication: spring security框架验证用户信息成功后的封装类。
*/
@Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException e) throws IOException {
//当框架验证用户信息失败时执行的方法
response.setContentType("text/json;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.println("{\"msg\":\"登陆失败!\"}");
writer.flush();
writer.close();
}
}
- MySuccessHandler
@Component("mySuccessHandler")
public class MySuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setContentType("text/json;charset=utf-8");
PrintWriter pw = response.getWriter();
pw.write("{\"msg\":\"登陆成功!\"}");
pw.flush();
pw.close();
}
}
- 配置文件添加自己创建的handler
@Configuration
@EnableWebSecurity
public class CustomSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private AuthenticationFailureHandler myFailureHandler;
@Autowired
private AuthenticationSuccessHandler mysuccessHandler;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/index","/mylogin.html","/login","/js/**").permitAll()//将要用到的地址排除
.antMatchers("/access/user/**").hasRole("USER")
.antMatchers("/access/read/**").hasRole("READ")
.antMatchers("/access/admin/**").hasRole("ADMIN")
.and()
.formLogin()
.successHandler(mysuccessHandler)//的定义登陆成功Handler
.failureHandler(myFailureHandler)//的定义登陆失败Handler
.loginPage("/myajax.html")//登陆的自定义页面
.loginProcessingUrl("/login")//登陆时提交的地址
.and()
.csrf().disable();//将跨域问题禁用
}
}
使用Json处理数据
-
导入依赖
使用 SpringBoot 就不要添加版本号,否则会版本冲突
-
返回结果
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result {
// 0 成功,1 失败
private int code;
//表示错误码
private int error;
//消息文本
private String msg;
}
- 使用 outputStream 输出数据
response.setContentType("text/json;charset=utf-8");
Result result = new Result();
result.setCode(1);
result.setError(1001);
result.setMsg("登陆失败");
ObjectMapper objectMapper = new ObjectMapper();
ServletOutputStream outputStream = response.getOutputStream();
objectMapper.writeValue(outputStream, result);
outputStream.flush();
outputStream.close();
验证码
- 因为使用的是 SpringSecurity ,所以将验证码 url 添加到白名单
- 代码
@RestController
@RequestMapping("/captcha")
public class CaptChaController {
//定义一个值,用来生成验证码的图片
//图像宽度 120像素
private int width = 120;
//图像高度 30 像素
private int height = 30;
//图片内容在图片的起始位置 12像素
private int drawY = 20;
//文字的间隔 18像素
private int space = 15;
//验证码有个文字
private int charCount = 6;
//验证码的内容数组
private String chars[] = {"A", "B", "C", "D", "E", "F",
"G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "T", "U", "V", "W",
"X", "Y", "Z", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"};
//定义方法:生成验证码内容。 在一个图片上,写入文字
@GetMapping("/code")
public void makeCaptchaCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
/*
验证码:需要在内存中绘制一个图片BufferedImage.
向这个图片中写入文字。 把绘制好内容的图片响应给请求
*/
//1.创建一个背景透明的图片,使用rgb表示颜色的
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
//2.获取画笔
Graphics g = image.getGraphics();
//3.1设置使用画笔是白颜色
g.setColor(Color.white);
//3.2给image画板都涂成白色的
// fillRect(矩形的起始x,矩形的起始y, 矩形的宽度,矩形的高度)
g.fillRect(0, 0, width, height);
//画内容
//4.创建一个字体
Font font = new Font("宋体", Font.BOLD, 16);
g.setFont(font);
g.setColor(Color.black);
//5.在画布上,写一个文字
//参数: 文字,x,y坐标
//g.drawString("中",10,drawY);
StringBuffer buffer = new StringBuffer("");
int ran = 0;
int len = chars.length;
for (int i = 0; i < charCount; i++) {
ran = new Random().nextInt(len);
buffer.append(chars[ran]);
//绘制文字获取随机颜色
g.setColor(makeColor());
g.drawString(chars[ran], (i + 1) * space, drawY);
}
//6.绘制干扰线
for (int m = 0; m < 4; m++) {
g.setColor(makeColor());
int dot[] = makeLineDot();
g.drawLine(dot[0], dot[1], dot[2], dot[3]);
}
//把生成的验证码存储到session中
request.getSession().setAttribute("code", buffer.toString());
//设置没有缓冲
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
response.setContentType("image/png");
OutputStream out = response.getOutputStream();
/*
RenderedImage im, 输出的图像
String formatName, 图像的格式 jpg,jpeg, png
ImageOutputStream output 输出到哪
*/
ImageIO.write(image, "png", out);
out.flush();
out.close();
}
//取随机颜色
private Color makeColor() {
Random random = new Random();
int r = random.nextInt(255);
int g = random.nextInt(255);
int b = random.nextInt(255);
return new Color(r, g, b);
}
//设置干扰线
private int[] makeLineDot() {
Random random = new Random();
int x1 = random.nextInt(width / 2);//起点
int y1 = random.nextInt(height);//起点
int x2 = random.nextInt(width);//终点
int y2 = random.nextInt(height);//终点
return new int[]{x1, y1, x2, y2};
}
}
- 修改 myajax.html 增加验证码
function changeCode() {
//new Date目的是浏览器不使用缓存,每次获取新的内容
var url="/captcha/code?t="+new Date();
$("#imagecode").attr("src",url);
}
<div >
用户名:<input type="text" id="username" value=""> <br/>
密 码:<input type="text" id="password" value=""> <br/>
验证码:<input type="text" id="txtcode" value=""> <br/>
<!--图像,显示验证码的值 -->
<img id="imagecode" src="/captcha/code" />
<a href="javascript:void(0)" onclick="changeCode()">重新获取</a>
<br/>
<br/>
<button id="btnLogin">使用ajax登录</button>
</div>
- 增加验证码参数
$(function () {
$("#btnLogin").click(function () {
alert("click");
var uname = $("#username").val();
var upwd = $("#password").val();
var txtcode = $("#txtcode").val();
$.ajax({
url: "/login",
type: "POST",
data: {
"username": uname,
"password": upwd,
"code": txtcode
},
dataType: "json",
success: function (resp) {
alert(resp.msg)
}
})
})
})
过滤器进行验证Code
- 使用的是过滤器,整个 Spring Security 框架就是一个过滤器实现的
- 用户发起请求,使用的过滤器:UsernamePasswordAuthenticationFilter
-
验证 username 和 password 之前,就应该先验证 code 是否正确,在UsernamePasswordAuthenticationFilter之前增加一个自定义过滤器,验证 session 中的 Code ,如果验证失败,排除异常
- 直接实现 Filter 接口
- 继承 只执行一次的过滤器
创建一个异常类
- 创建异常类继承 AuthenticationException
public class VerificationException extends AuthenticationException {
public VerificationException(String msg, Throwable cause) {
super(msg, cause);
}
public VerificationException(String msg) {
super(msg);
}
public VerificationException() {
super("验证错误,请重新输入");
}
}
- 创建过滤器类继承 OncePerRequestFilter
public class VerificationCodeFilter extends OncePerRequestFilter {
private MyFailureHandler failureHandler = new MyFailureHandler();
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String uri = request.getRequestURI();
if (!("/login".equals(uri))) {
filterChain.doFilter(request, response);
} else {
try {
verifcatioinCode(request);
filterChain.doFilter(request, response);
} catch (VerificationException e) {
Result result = new Result();
result.setCode(1);
result.setError(1002);
result.setMsg("验证码错误!!!");
failureHandler.setResult(result);
failureHandler.onAuthenticationFailure(request, response, e);
}
}
}
private void verifcatioinCode(HttpServletRequest request) {
HttpSession session = request.getSession();
String requestCode = request.getParameter("code");
Object attrCode = session.getAttribute("code");
String sessionCode = "";
if (attrCode != null) {
sessionCode = (String) attrCode;
}
System.out.println("VerificationCodeFilter doFilterInternal requestCode:" + requestCode + "|sessionCode:" + sessionCode);
// if (!StringUtils.isEmpty(sessionCode)) {
// //session中存在code,就清除
// session.removeAttribute("code");
// }
if (StringUtils.isEmpty(requestCode) || StringUtils.isEmpty(sessionCode) || !requestCode.equals(sessionCode)) {
throw new VerificationException();
}
}
}
- 自定义过滤器添加到过滤器链中
@Configuration
@EnableWebSecurity
public class CustomSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private AuthenticationFailureHandler myFailureHandler;
@Autowired
private AuthenticationSuccessHandler mysuccessHandler;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/index", "/mylogin.html", "/login", "/js/**", "/login.jsp").permitAll()//将要用到的地址排除
.antMatchers("/access/user/**").hasRole("USER")
.antMatchers("/access/read/**").hasRole("READ")
.antMatchers("/access/admin/**").hasRole("ADMIN")
.and()
.formLogin()
.successHandler(mysuccessHandler)
.failureHandler(myFailureHandler)
.loginPage("/myajax.html")//登陆的自定义页面
.loginProcessingUrl("/login")//登陆时提交的地址
.and()
.csrf().disable()//将跨域问题禁用
//自定义过滤器添加到过滤器链中
.addFilterBefore(new VerificationCodeFilter(), UsernamePasswordAuthenticationFilter.class);
}
}