前言
本来之前以为springsecurity差不多就只是简简单单的拦截器的功能,当时就只是随便的了解了一下 就觉得自己会用了 但是最近在做项目 需要用到权限管理,第一时间就想到了springsecurity ,问题是这个和我想的拦截器的功能还是相差很大 的 ,由于是第一次使用这个框架 理解起来也是很费力 更何况各个平台里面的博主由于有些面向的是工作好几年的程序员所以代码看起来就很费力 ,对新手很不友好 再加上这里面的单词一大堆 这下更是看天书了,撸了一段时间的源码 感觉对springsecurity的理解还是深了一点 所以记录一下。
背景
学一个东西我认为需要一个需要它的背景,就像我现在这样,我需要一个工具 能帮我将不同登录的用户所能访问的不同的网页进行管理 ,比如 普通用户只能访问普通网页 ,充值了的用户可以访问高级一点的网页 ,但是同时他也能访问普通的网页。 而且顶级用户能直接封停普通用户的账号 俗称高人一等。那么 我该如何实现呢?答案就是springsecurity!
使用
首先我们需要新建一个springboot项目 ,然后导入依赖
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.org</groupId>
<artifactId>springsecurity_demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springsecurity_demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.4.RELEASE</spring-boot.version>
</properties>
<dependencies>
<!--转换成json字符串的工具-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.2</version>
</dependency>
<!--springboot集成web操作-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--springsecurity-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--mybatis-plus依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<!--模板引擎-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.2</version>
</dependency>
<!--springboot-自带测试工具-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!---->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.3.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.3.4.RELEASE</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.4.RELEASE</version>
<configuration>
<mainClass>com.org.SpringsecurityDemoApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
主要是里面的springsecurity的启动器要进去 还有就是这里使用的是MyBatisplus 使用MyBatis也能操作的 。
然后就是我们的用户类
package com.org.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.apache.ibatis.mapping.FetchType;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
//@EqualsAndHashCode(callSuper = false)
public class User implements UserDetails {
注意 这里实现了一个userdetails 实际上就是一个验证身份的工具
后面使用到他的时候 会调用里面的方法和属性 来判断这个类的身份是什么
其他的属性可有可无 主要是要有name password 还有role
他会调用里面的方法 来和我们数据库里面的user进行比对 然后再赋予权限。
当然 也可以不在这里实现这个接口 可以使用构造方法将这三个元素传进去
这里实际上还有四个布尔类型的属性我没写上去
下面的判断就是基于那四个布尔型来进行判断的 但是由于我不需要那些实现就没有写上去 而是直接进行判断了。
private String id;
private String name;
private String password;
String age;
private String role;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(this.role));
return authorities;
这里就是这个角色 我的理解就是 将他的角色装到这个容器里面 然后
我们授权的时候就更具这个容器里面的角色来进行授权
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return name;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
package com.org.serviceImpl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.org.entity.User;
import com.org.mapper.UserMapper;
import com.org.service.UserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
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.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.*;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService,UserDetailsService {
@Autowired
UserMapper userMapper;
这里需要注意的就是 这里是service层 我们需要实现这个UserDetailsService接口
然后重写下面这个方法 这个方法的功能就是springsecurity和MyBatis整合的地方 通过
注入 将我们的Mapper注入到这个类里面 然后在这个方法里面调用 通过传进来的
用户的名字 到数据库里面查询到这个用户的所有信息 然后返回 将这个用户的角色 密码名字
都封装成一个userdetails进行返回 我们已经完成认证的功能了 那么接下来就是授权了
什么样的用户能干什么 就需要我们再手动编写一个配置类config
//加载用户
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//mybatis-plus帮我们写好了sql语句,相当于 select * from user where account ='${account}'
QueryWrapper<User> wrapper=new QueryWrapper<>();
wrapper.eq("name",s);
User user=userMapper.selectOne(wrapper); //user即为查询结果
if(user==null){
throw new UsernameNotFoundException("用户名错误!!");
}
//获取用户权限,并把其添加到GrantedAuthority中
List<GrantedAuthority> grantedAuthorities=new ArrayList<>();
GrantedAuthority grantedAuthority=new SimpleGrantedAuthority(user.getRole());
grantedAuthorities.add(grantedAuthority);
return user;
//方法的返回值要求返回UserDetails这个数据类型, UserDetails是接口,找它的实现类就好了
//new org.springframework.security.core.userdetails.User(String username,String password,Collection<? extends GrantedAuthority> authorities) 就是它的实现类
// return new org.springframework.security.core.userdetails.User(s,user.getPassword(),grantedAuthorities);
}
}
package com.org.configure;
import com.org.Handler.*;
import com.org.serviceImpl.UserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.builders.HttpSecurity;
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;
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //开启权限注解,默认是关闭的
public class SpringsecurityConfig extends WebSecurityConfigurerAdapter {
首先继承security的配置
下面这些是对一些情况进行处理的逻辑 比如 我登录成功了 我需要干什么
我密码不对需要干什么 我登录失败了 需要干什么 这些就是下面这些类来进行实现。
@Autowired
AuthenticationEnryPoint authenticationEnryPoint;
//未登录
@Autowired
AuthenticationSuccess authenticationSuccess;
//登录成功
@Autowired
AuthenticationFailure authenticationFailure;
//登录失败
@Autowired
AuthenticationLogout authenticationLogout;
//注销
@Autowired
AccessDeny accessDeny;
//无权访问
@Autowired
SessionInformationExpiredStrategy sessionInformationExpiredStrategy;
//检测异地登录
@Autowired
SelfAuthenticationProvider selfAuthenticationProvider;
//自定义认证逻辑处理
下面这个就是我们刚刚编写的service 我们将这个userdetilsservice的实现类注册成一个bean 然后
就能注入到这个config里面去了
@Override
@Bean
public UserDetailsService userDetailsService() {
return new UserServiceImpl();
}
//加密方式
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
//认证
这里是我们自定义的认证逻辑 我们刚刚在service层里面重写的那个方法
里面查询到的是我们的用户全部属性 但是我们怎么确保他的密码是否正确呢?
我们这时候就需要认证一下通过这个方法 将前端输入的账号进行查询 然后将那个密码
和我们的输入的密码 进行比对 因为我们是加密过的 所以我们只需要调用这个
类的Match方法 如果比对成功的话 就ok了
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(selfAuthenticationProvider);
}
//授权
@Override
protected void configure(HttpSecurity http) throws Exception {
//cors()解决跨域问题,csrf()会与restful风格冲突,默认springsecurity是开启的,所以要disable()关闭一下
http.cors().and().csrf().disable();
// /index需要权限为ROLE_USER才能访问 /hello需要权限为ROLE_ADMIN才能访问
http.authorizeRequests()
.antMatchers("/index").hasRole("USER")
.antMatchers("/hello").hasRole("ADMIN")
这里就是分配角色的功能
.and()
.formLogin() //开启登录
.permitAll() //允许所有人访问
.successHandler(authenticationSuccess) // 登录成功逻辑处理
.failureHandler(authenticationFailure) // 登录失败逻辑处理
.and()
.logout() //开启注销
.permitAll() //允许所有人访问
.logoutSuccessHandler(authenticationLogout) //注销逻辑处理
.deleteCookies("JSESSIONID") //删除cookie
.and().exceptionHandling()
.accessDeniedHandler(accessDeny) //权限不足的时候的逻辑处理
.authenticationEntryPoint(authenticationEnryPoint) //未登录是的逻辑处理
.and()
.sessionManagement()
.maximumSessions(1) //最多只能一个用户登录一个账号
.expiredSessionStrategy(sessionInformationExpiredStrategy) //异地登录的逻辑处理
;
}
}
流程
从全局来看 我们的springsecurity的工作流程是什么样子的? 首先 当我们从前端输入我们需要访问的网址的时候 会发出get或者post请求 这个请求被web服务器解析封装成request请求 然后这个请求会被拦截 检查里面信息看是否登录 如果没登录 会跳转到登录 如果这个请求是登录请求 那么必须是一个post请求 而且form表单里面的请求是“login.html” 这个可以自己去springsecurity里面定义的, 接下来springsecurity会取出其中的username和password属性 这个也是可以定义的 默认是这个 然后去数据库里面找到这个user 将其中的role属性取出来 和username,password封装成一个userdetile 返回出来 。 前端传来的数据会被封装成一个Authentication的实现类 然后从里面取出他的username和他的password 调用我们在springsecurity配置类里面设置的那个加密方式 的比对方法 看看这两个密码是否相同。如果相同 那么验证的功能就已经完成了 我们将userdetil的role取出来封装成一个UsernamePasswordAuthenticationToken 令牌 有了这个令牌我们就能完成授权的操作 我们从配置类里面设置 访问某个接口 需要某些令牌 这样只有拥有这些令牌的请求才能实现访问。