Spring Security
Spring Security 应用级别的安全主要包含两个主要部分,即登录认证(Authentication)和访问授权(Authorization),首先用户登录的时候传入登录信息,登录验证器完成登录认证并将登录认证好的信息存储到请求上下文,然后再进行其他操作,如在进行接口访问、方法调用时,权限认证器从上下文中获取登录认证信息,然后根据认证信息获取权限信息,通过权限信息和特定的授权策略决定是否授权。
入门案例(伪:未连接数据库)
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
如果这个时候启动页面,SpringSecurity会进入指定的登录页面,如果不登录,就无法访问其他页面,密码会在控制台自动输出,密码是使用uuid自动生成的,user用户名是指定好的user
如果想指定用户名、密码,可以在配置文件中配置用户名和密码
spring.security.user.name=admin
spring.security.user.password=123
此时启动项目,用户名密码就会是指定的用户名和密码
编写Config,设置不拦截页面和拦截页面
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//不过滤/请求, /:控制器Controller不拦截
.antMatchers("/")
.permitAll()
//过滤,过滤一些css、images自定义
.anyRequest()
.authenticated()
.and()
//登录
.formLogin()
//自定义登录页面
.loginPage("/login.html")
//登录请求,form表单提交到这个地址
.loginProcessingUrl("/login")
//登录成功之后跳转页面
.defaultSuccessUrl("/title")
.permitAll()
.and()
//退出
.logout().permitAll()
.and()
//全源跨域
.csrf()
.ignoringAntMatchers("/logout");
}
}
在Controller控制器
@RequestMapping("/")
public String getIndex(HttpSession session){
session.setAttribute("username","zs");
return "index";
}
@RequestMapping("/title")
public String Login(){
return "title";
}
@RequestMapping("/login.html")
public String loginView(){
return "login";
}
编写login.html
<div th:if="${param.error}">
<span>登录失败</span>
</div>
<!--地址栏-->
<div th:if="${param.logout}">
<span>注销成功</span>
</div>
<!--action:登录地址-->
<form method="post" th:action="@{login}">
<table>
<tr>
<td>用户名:</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="登录"></td>
</tr>
</table>
</form>
此时,可以使用properties中的用户名密码登录进去
不在配置文件中设置用户名和密码,在配置类中配置在内存中
在SecurityConfig中配置用户信息,配置用户的权限
package com.swj.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.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//不过滤/请求
.antMatchers("/")
.permitAll()
//设置admin和user的权限
///admin必须是有ADMIN权限才可以访问
.antMatchers("/admin").hasRole("ADMIN")
///user用户所有权限都可以访问
.antMatchers("/user").access("hasRole(\"ADMIN\") or hasRole(\"USER\")")
//过滤
.anyRequest()
.authenticated()
.and()
//登录
.formLogin()
//自定义登录页面
.loginPage("/login.html")
//登录请求
.loginProcessingUrl("/login")
//登录成功之后跳转页面
.defaultSuccessUrl("/title")
.permitAll()
.and()
//退出
.logout().permitAll()
.and()
//全源跨域
.csrf()
.ignoringAntMatchers("/logout");
}
//本地伪,存储在本地的数据
@Autowired
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//在内存中创建用户信息、密码
auth.inMemoryAuthentication()
//设计加密方式使用Security自带加密BCryptPasswordEncoder
.passwordEncoder(new BCryptPasswordEncoder())
//创建用户、密码
.withUser("admin")
.password(new BCryptPasswordEncoder().encode("123"))
//赋予权限
.roles("ADMIN", "USER")
.and()
.withUser("user").password(new BCryptPasswordEncoder().encode("123"))
.roles("USER");
}
}
编写user、admin两个页面
<h1>欢迎你:<span th:text="${session.username}"></span></h1>
<hr>
<a th:href="@{/admin}" th:text="管理员"></a>
<a th:href="@{/user}" th:text="普通用户"></a>
<hr>
<!--自定义页面一定要使用post请求退出-->
<form th:method="post" th:action="@{logout}">
<input type="submit" th:value="退出">
</form>
<h1>这里是普通用户</h1>
<hr>
<a th:href="@{/title}">返回首页</a>
<hr>
<!--自定义页面一定要使用post请求退出-->
<form th:method="post" th:action="@{logout}">
<input type="submit" th:value="退出">
</form>
<h1>这里是管理员页面</h1>
<hr>
<a th:href="@{/title}" th:text="返回首页"></a>
<hr>
<!--自定义页面一定要使用post请求退出-->
<form th:method="post" th:action="@{logout}">
<input type="submit" th:value="退出">
</form>
登录不用的用户,进入不同的权限页面,普通用户无法进入管理员页面,但是管理员可以进入所有页面
入门案例(真:连接数据库)
Spring Security做权限不能老是把权限用户写死,那么就需要数据库来进行持久化的操作
sql.xml
CREATE DATABASE `user_role`;
USE `user_role`;
//创建用户表
CREATE TABLE sys_user
(
sys_uid INT PRIMARY KEY auto_increment,
sys_uName varchar(255) not null,
sys_password varchar(255) not null
);
insert into sys_user values (0, 'admin', '123');
insert into sys_user values (0, 'root', '123');
//创建角色表
create table sys_role
(
sys_rid int primary key auto_increment,
sys_rName varchar(255) not null
);
insert into sys_role
values (0, 'MANAGER_ADMIN'),(0, 'SYSTEM_ADMIN'),(0, 'USER');
//设计多对多关系
create table sys_ur
(
sys_uRid int primary key auto_increment,
sys_uid int not null,
sys_rid int not null
);
insert into sys_ur values (0, 1, 1);
insert into sys_ur values (0, 1, 2);
insert into sys_ur values (0, 1, 3);
insert into sys_ur values (0, 2, 2);
执行查询多对多sql语句
select
sys_user.sys_uid, sys_user.sys_uName, sys_user.sys_password,
sys_role.sys_rid, sys_role.sys_rName
from sys_ur,
sys_user,
sys_role
where sys_ur.sys_rid = sys_role.sys_rid
and sys_ur.sys_uid = sys_user.sys_uid
and sys_uName=#{username}
多对多:没有绝对的多对多,只是在多对一、一对多的基础变化了一点
多对多在mybatis或者在jpa中实现其实很简单,只需要在指定的两个类中加上相对应的List保存对象的集合
例如:
有两个实体类,Student、Teache
一个老师可以有多个学生,一个学生可以有多个老师
在老师的类中加上一个List集合
List<Student> list=new ArrayList<Student>();
在学生的类中加上一个List集合
List<Teacher> list=new ArrayList<Teacher>();
在随便一个mapper文件中做一对多collection
这样就可以实现多对多的功能了
回到Spring Security连接数据库
application.xml
server.port=8080
spring.datasource.url=jdbc:mysql://localhost:3306/user_role?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.username=root
spring.datasource.password=ok
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.thymeleaf.cache=false
mybatis.type-aliases-package=com.swj.entity
mybatis.mapper-locations=classpath:mapper/*.xml
创建两个实体类,SysUser、SysRole
@Data
public class SysRole implements Serializable {
private static final long serialVersionUID = 972452457573123140L;
private Integer sysRid;
private String sysRname;
private List<SysUser> sysUser;
}
@Data
public class SysUser implements Serializable {
private static final long serialVersionUID = -33590531515538724L;
private Integer sysUid;
private String sysUname;
private String sysPassword;
private List<SysRole> sysRole;
}
创建dao层,根据用户查询指定用户信息和指定用户权限等信息
List<SysUser> loginUser(String username);
mapper.xml
<resultMap id="SysUserMap" type="SysUser">
<id column="sys_uid" property="sysUid"></id>
<result column="sys_uName" property="sysUname"></result>
<result column="sys_password" property="sysPassword"></result>
<collection property="sysRole" ofType="SysRole">
<id column="sys_rid" property="sysRid"></id>
<result column="sys_rName" property="sysRname"></result>
</collection>
</resultMap>
<!--根据用户查询指定用户信息和指定用户权限等信息-->
<select id="loginUser" parameterType="string" resultMap="SysUserMap">
select
sys_user.sys_uid, sys_user.sys_uName, sys_user.sys_password,
sys_role.sys_rid, sys_role.sys_rName
from sys_ur,
sys_user,
sys_role
where sys_ur.sys_rid = sys_role.sys_rid
and sys_ur.sys_uid = sys_user.sys_uid
and sys_uName=#{username}
</select>
在service中我们要开始实现Spring Security了,实现用户的登录和用户的授权
创建一个SysUserServiceImpl.java,实现UserDetailsService,实现里面的方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
package com.swj.service.impl;
import com.sun.javafx.scene.transform.TransformUtils;
import com.swj.dao.SysUserMapper;
import com.swj.entity.SysRole;
import com.swj.entity.SysUser;
import com.swj.service.SysUserService;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.*;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@Service("sysUserService")
public class SysUserServiceImpl implements SysUserService, UserDetailsService {
@Resource
private SysUserMapper mapper;
@Override
public List<SysUser> queryAll() {
return mapper.queryAll();
}
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//s是用户名
/**
1.根据用户名查询用户信息和权限
2.判断用户是否存在
3.循环赋值权限,hasRole中默认权限前面要加上ROLE_
4.使用User判断用户账户密码进行登录,和权限授予
*/
UserDetails userDetails = null;
//根据用户名查询用户信息,判断是否登录成功
List<SysUser> sysUsers = this.mapper.loginUser(s);
//判断是否查询到了当前用户
if (sysUsers == null) {
throw new UsernameNotFoundException("对不起,改用户名不存在,请重新输入");
}
//获取权限
Collection<GrantedAuthority> authorities = new ArrayList<>();
for (SysUser sysUser : sysUsers) {
List<SysRole> sysRole = sysUser.getSysRole();
for (int i = 0; i < sysRole.size(); i++) {
//权限
authorities.add(new SimpleGrantedAuthority("ROLE_"+sysRole.get(i).getSysRname()));
//hasRole中需要使用前缀ROLE_
System.out.println("ROLE_"+sysRole.get(i).getSysRname());
}
}
UserDetails build = User.withUsername(
sysUsers.get(0).getSysUname())
.password(new BCryptPasswordEncoder().encode(sysUsers.get(0).getSysPassword()))
.authorities(authorities).build();
return build;
}
}
SecurityConfig.java
package com.swj.config;
import com.swj.entity.SysUser;
import com.swj.service.SysUserService;
import com.swj.service.impl.SysUserServiceImpl;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.annotation.Resource;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//注入前面实现的UserDetailsService的类
@Resource
private SysUserServiceImpl sysUserService;
//重:没有此处无法实现用户的登录
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//设置信息
auth.userDetailsService(sysUserService).passwordEncoder(password());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//设置权限
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/ManagerAdmin/").hasRole("MESSAGE_ADMIN")
.antMatchers("/SystemAdmin/").access("hasRole('MESSAGE_ADMIN') or hasRole('SYS_ADMIN')")
.antMatchers("/USER/").access("hasRole('MESSAGE_ADMIN') or hasRole('SYS_ADMIN') or hasRole('USER')")
.anyRequest().authenticated()
.and().formLogin()
.loginPage("/login")
.loginProcessingUrl("/login")
.defaultSuccessUrl("/")
.permitAll()
.and().logout()
.permitAll();
}
//密码加密
@Bean
PasswordEncoder password() {
return new BCryptPasswordEncoder();
}
}
创建各式各样的Controller和页面
@Controller
@RequestMapping("/ManagerAdmin")
public class ManagerAdminController {
@RequestMapping("/")
public String getIndex(){
return "Manageradmin";
}
}
@Controller
@RequestMapping("/SystemAdmin")
public class SystemAdminController {
@RequestMapping("/")
public String getIndex(){
return "Systemadmin";
}
}
@Controller
@RequestMapping("/USER")
public class SysUserController {
@Resource
private SysUserMapper mapper;
@RequestMapping("/")
public List<SysUser> queryAll(){
return this.mapper.queryAll();
}
}
@Controller
public class IndexController {
@RequestMapping("/")
public String getIndex(){
return "index";
}
@RequestMapping("/login")
public String login(){
return "login";
}
}
创建不同的页面
login.html
<form th:action="@{login}" method="post">
<table>
<tr>
<td>账号</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>密码</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="登录"></td>
</tr>
</table>
</form>
index.html
<a th:href="@{/ManagerAdmin/}">Manageradmin.html</a><br>
<a th:href="@{/SystemAdmin/}">Systemadmin.html</a><br>
<a th:href="@{/USER/}">user.html</a>
省略后面的页面ManagerAdmin.html、SystemAdmin.html、USER.html
到此结束了入门的全部案例,SpringSecurity可以连接数据库并且根据用户的权限进入不同的页面,没有权限不可以进入页面
自定义无权限页面
可以在配置类中 SecurityConfig.java 加入一句话
http..exceptionHandling().accessDeniedPage("/403");
此时在无权限的时候会跳到403请求,在Controller中定义一个403的请求,跳转到指定的403页面
基于注解的形式实现权限
想使用注解的方式实现权限需要在配置类或者启动类上加一个注解开启Spring Security的注解
//true:代表开启注解
@EnableGlobalMethodSecurity(securedEnabled = true)
在配置文件中把代码实现权限的地方去掉,不需要使用了,我们这里使用的是注解方式实现
不需要使用这些,太过于复杂
// .antMatchers("/ManagerAdmin/").hasRole("MESSAGE_ADMIN")
// .antMatchers("/SystemAdmin/").access("hasRole('MESSAGE_ADMIN') or hasRole('SYS_ADMIN')")
// .antMatchers("/USER/").access("hasRole('MESSAGE_ADMIN') or hasRole('SYS_ADMIN') or hasRole('USER')")
注解方式实现上面代码
在控制器上加上指定的权限比如@Secured({"ROLE_MESSAGE_ADMIN","ROLE_SYS_ADMIN"})
,需要有MESSAGE_ADMIN、SYS_ADMIN的权限才可以访问该控制器,ROLE_是hasRole必须指定的
@Controller
@RequestMapping("/ManagerAdmin")
public class ManagerAdminController {
@Secured({"ROLE_MESSAGE_ADMIN"})
@RequestMapping("/")
public String getIndex(){
return "Manageradmin";
}
}
@Controller
@RequestMapping("/SystemAdmin")
public class SystemAdminController {
@Secured({"ROLE_MESSAGE_ADMIN","ROLE_SYS_ADMIN"})
@RequestMapping("/")
public String getIndex(){
return "Systemadmin";
}
}
@Controller
@RequestMapping("/USER")
public class SysUserController {
@Resource
private SysUserMapper mapper;
@Secured({"ROLE_MESSAGE_ADMIN","ROLE_SYS_ADMIN","ROLE_USER"})
@RequestMapping("/")
public List<SysUser> queryAll(){
return this.mapper.queryAll();
}
}
如果开启了jsr250Enabled注解
@EnableGlobalMethodSecurity(jsr250Enabled = true)
jsr250Enabled中开启了3个注解
@DenyAll 拒绝所有访问
@RolesAllowed({"USER", "ADMIN"}) 该方法只要具有"USER", "ADMIN"任意一种权限就可以访问。这里可以省 略前缀ROLE_,实际的权限可能是ROLE_ADMIN
@PermitAll 允许所有访问
prePostEnabled注解
@EnableGlobalMethodSecurity(prePostEnabled= true)
百度去,自己百度查省略
记住我、自动登录
如果想实现自动登录、记住我等功能需要记住一个类JdbcTokenRepositoryImpl
,点开源码看一下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sQBU2Wm3-1604471984333)
Spring Security实现自动登录记住我是通过cookie和数据库一起实现的,在登录页面中点击记住我,会自动在数据库中创建一张表,记录信息
开始:
记住我、自动登录是基于cookie和数据库实现的所有我们要注入数据源
@Resource
private DataSource dataSource;
//下面做关于记住我的相关配置
@Bean
public PersistentTokenRepository persistentTokenRepository() {
//通过它自动生成sql存储到数据库表中
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
//数据源
tokenRepository.setDataSource(dataSource);
//是否自动创建表
tokenRepository.setCreateTableOnStartup(true);
return tokenRepository;
}
下面在主配置中配置记住我的相关配置
//开启记住我
http.rememberMe().
//调用上面生成数据库生成token的方法
tokenRepository(persistentTokenRepository())
//cookie时长,以秒为单位
.tokenValiditySeconds(60)
//存储信息
.userDetailsService(sysUserService)
html页面中添加单选框checkbox,这个checkbox中的name必须是指定的不能修改
<!DOCTYPE html>
<html lang="en" xmlns:th="">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form th:action="@{login}" method="post">
<table>
<tr>
<td>账号</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>密码</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td>记住我</td>
<td><input type="checkbox" name="remember-me"></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="登录"></td>
</tr>
</table>
</form>
</body>
</html>
检查是否成功
没有点击记住我
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8Pto6ICH-1604471984335)
点击记住我
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hzkLVNko-1604471984338)
成功实现了记住我、自动登录功能
完结,某些人不想看就不要看,抵制颜池。