this.loginPage + “" />二、用户认证2.1 导入依赖与配置 基于Spring Initializr
创建SpringBoot
项目(本次案例采用Spring Boot 2.7.12版本为例),导入基本依赖:
org.springframework.bootspring-boot-starter-securityorg.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-starter-freemarkerorg.projectlomboklomboktruecom.baomidoumybatis-plus-boot-starter3.5.2com.baomidoumybatis-plus-generator3.5.2com.alibabafastjson2.0.32com.mysqlmysql-connector-jruntime
spring-boot-starter-security
包含了以下几个主要的依赖:
spring-security-core :Spring Security
的核心模块,提供了基于权限的访问控制以及其他安全相关功能。
spring-security-config :提供了Spring Security
的配置实现,例如通过Java配置创建安全策略和配置Token存储等。
spring-security-web :提供了Spring Security Web
的基本功能,例如Servlet
集成和通过HttpSecurity
配置应用程序安全策略。
配置application.yml
文件:
spring:datasource:type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.jdbc.Driverusername: rootpassword: 123456url: jdbc:mysql://localhost:3306/bookshop?useUnicode=true&characterEncoding=utf8&useSSL=falsefreemarker:enabled: truesuffix: .ftltemplate-loader-path: classpath:/templates/mybatis-plus:# Mybatis Mapper所对应的XML位置mapper-locations: classpath:mapper/*.xml# 别名包扫描路径type-aliases-package: com.ycxw.springsecurity.entity# 是否开启自动驼峰命名规则(camel case)映射configuration:map-underscore-to-camel-case: trueglobal-config:db-config:logic-delete-field: deleted # 全局逻辑删除的实体字段名logic-delete-value: 1 # 逻辑已删除值(默认为 1)logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)logging:level:com.jun.security01.mapper: debug
2.2用户对象UserDetails 首先准备一张用户表,通过mybatis-plus生成代码后修改User类
并实现UserDetails接口
。
package com.ycxw.springsecurity.entity;import com.baomidou.mybatisplus.annotation.IdType;import com.baomidou.mybatisplus.annotation.TableField;import com.baomidou.mybatisplus.annotation.TableId;import com.baomidou.mybatisplus.annotation.TableName;import io.swagger.annotations.ApiModel;import io.swagger.annotations.ApiModelProperty;import lombok.Getter;import lombok.Setter;import lombok.experimental.Accessors;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.userdetails.UserDetails;import java.io.Serializable;import java.time.LocalDateTime;import java.util.List;/** * 用户信息表(继承UserDetails类) * * @author 云村小威 * @since 2023-12-21 */@Getter@Setter@Accessors(chain = true)@TableName("sys_user")@ApiModel(value = "User对象", description = "用户信息表")public class User implements Serializable, UserDetails {private static final long serialVersionUID = 1L;@ApiModelProperty("唯一标识")@TableId(value = "id", type = IdType.AUTO)private Integer id;@ApiModelProperty("用户账号")@TableField("username")private String username;@ApiModelProperty("用户密码")@TableField("password")private String password;@ApiModelProperty("真实姓名")@TableField("real_name")private String realName;@ApiModelProperty("身份证号")@TableField("id_card")private String idCard;@ApiModelProperty("性别,男或女")@TableField("gender")private String gender;@ApiModelProperty("家庭住址")@TableField("address")private String address;@ApiModelProperty("联系电话")@TableField("phone")private String phone;@ApiModelProperty("创建时间")@TableField("create_date")private LocalDateTime createDate;/** * 是否过期 */@TableField("account_non_expired")private boolean accountNonExpired;/** * 存放用户的权限(不存放在数据库中) */@TableField(exist = false)private List authorities;/** * 是否锁定 */@TableField("account_non_locked")private boolean accountNonLocked;/** * 是否过期 */@TableField("credentials_non_expired")private boolean credentialsNonExpired;/** * 是否启用 */@TableField("enabled")private boolean enabled;}
实现UserDatails接口会重写它的五个方法,如该类最后的五个属性,除authorities属性以外,请将其他四个属性加入数据库表中(原用户表未有该字段,通过实现UserDatails后需要身份验证和授权则要添加)
UserDetails
是Spring Security框架中的一个接口,它代表了应用程序中的用户信息。UserDetails
接口定义了一组方法,用于获取用户的用户名、密码、角色和权限等信息,以便Spring Security可以使用这些信息进行身份验证和授权。
以下是UserDetails
接口中定义的方法:
getUsername()
:获取用户的用户名。
getPassword()
:获取用户的密码。
getAuthorities()
:获取用户的角色和权限信息。
isEnabled()
:判断用户是否可用。
isAccountNonExpired()
:判断用户的账号是否过期。
isAccountNonLocked()
:判断用户的账号是否被锁定。
isCredentialsNonExpired()
:判断用户的凭证是否过期。
自定义用户信息时,可以实现UserDetails
接口并覆盖其中的方法来提供自己的用户信息。
2.3业务对象UserDetailsService 修改UserServiceImpl
并实现UserDetailsService
,重写loadUserByUsername(String username)
方法。
package com.ycxw.springsecurity.service.impl;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.ycxw.springsecurity.entity.User;import com.ycxw.springsecurity.mapper.UserMapper;import com.ycxw.springsecurity.service.IUserService;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.Objects;/** * 用户信息表 服务实现类 * * @author 云村小威 * @since 2023-12-21 */@Servicepublic class UserServiceImpl extends ServiceImpl implements IUserService, UserDetailsService {@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 根据用户名查询用户User user = getOne(new QueryWrapper().eq("username", username));//判断用户是否存在if (Objects.isNull(user))throw new UsernameNotFoundException("用户不存在");return user;}}
UserDetailsService
是Spring Security中的一个接口,它用于从特定数据源(如数据库)中获取用户详细信息,以进行身份验证和授权。实现该接口的类需要实现loadUserByUsername
方法,该方法根据给定的用户名返回一个UserDetails
对象,该对象包含有关用户的详细信息,例如密码、角色和权限等。在Spring Security中,UserDetailsService
通常与DaoAuthenticationProvider
一起使用,后者是一个身份验证提供程序,用于验证用户的凭据。
2.4 SecurityConfig配置 2.4.1BCryptPasswordEncoder密码编码器 Spring Security提供了多种密码加密方式,大致可以归类于以下几种:
对密码进行明文处理,即不采用任何加密方式;
采用MD5加密方式;
采用哈希算法加密方式;
BCryptPasswordEncoder
是Spring Security
中一种基于bcrypt
算法的密码加密方式。bcrypt
算法是一种密码哈希函数,具有防止彩虹表攻击的优点,因此安全性较高。
使用BCryptPasswordEncoder
进行密码加密时,可以指定一个随机生成的salt
值(俗称:加盐) ,将其与原始密码一起进行哈希计算。salt值可以增加密码的安全性,因为即使两个用户使用相同的密码,由于使用不同的salt
值进行哈希计算,得到的哈希值也是不同的。
在Spring Security
中,可以通过在SecurityConfig
配置类中添加以下代码来使用BCryptPasswordEncoder
进行密码加密:
@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}
这样就可以在Spring Security中使用BCryptPasswordEncoder
进行密码加密了。
相比BCryptPasswordEncoder密码编码器之下明文和MD5加密的缺点是?
明文的缺点:
安全性低: 明文存储密码非常不安全,因为任何有权访问数据库的人都能够看到用户的密码。容易受到攻击: 明文存储密码很容易受到攻击,例如暴力破解攻击和彩虹表攻击。MD5 加密的缺点:
安全性低: MD5 算法是一种弱加密算法,很容易被破解。不可逆: MD5 加密是不可逆的,这意味着无法从哈希值中恢复明文密码。容易受到碰撞攻击: MD5 算法容易受到碰撞攻击,这意味着可以找到两个不同的输入,它们产生相同的哈希值。(可根据相同加密后的密码找出明文密码) 因此,明文和 MD5 加密都不适合用于保护用户密码。
2.4.2RememberMe 记住登录信息 在实际开发中,为了用户登录方便常常会启用记住我(Remember-Me
)功能。如果用户登录时勾选了“记住我”选项,那么在一段有效时间内,会默认自动登录,免去再次输入用户名、密码等登录操作。该功能的实现机理是根据用户登录信息生成 Token
并保存在用户浏览器的 Cookie
中,当用户需要再次登录时,自动实现校验并建立登录态的一种机制。
Spring Security
提供了两种 Remember-Me
的实现方式:
rememberMe
主要方法介绍:
方法 说明 rememberMeParameter()
指定在登录时“记住我”的 HTTP
参数,默认为 remember-me
tokenValiditySeconds()
设置 Token
有效期为 200s,默认时长为 2 星期 tokenRepository()
指定 rememberMe
的 token 存储方式,可以使用默认的 PersistentTokenRepository
或自定义的实现 userDetailsService()
指定 UserDetailsService
对象 rememberMeCookieName()
指定 rememberMe
的 cookie
名称
基于持久化Token配置 :
Remember-Me
功能的开启需要在configure(HttpSecurity http)
方法中通过http.rememberMe()
配置,该配置主要会在过滤器链中添加 RememberMeAuthenticationFilter
过滤器,通过该过滤器实现自动登录。
// 注入用户服务@Autowiredprivate UserDetailsService userDetailsService;@Autowiredprivate ObjectMapper objectMapper;// 注入数据源(spring自带)@Resourcepublic DataSource dataSource;// 创建持久令牌存储库@Beanpublic PersistentTokenRepository persistentTokenRepository() {// 创建一个JdbcTokenRepositoryImpl实例JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();// 设置数据源tokenRepository.setDataSource(dataSource);// 设置启动时创建表tokenRepository.setCreateTableOnStartup(false);// 返回tokenRepositoryreturn tokenRepository;}/* * 安全过滤器链 * */@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/").permitAll()// 设置角色权限//.antMatchers("/admin/**").hasRole("ADMIN").antMatchers("/user/**").hasAnyRole("ADMIN", "USER")//其他所有请求都需要用户进行身份验证。.anyRequest().authenticated().and().formLogin()// 设置登录页面的 URL.loginPage("/")// 设置登录请求的 URL,即表单提交的 URL.loginProcessingUrl("/userLogin")// 设置登录表单中用户名字段的参数名,默认为username.usernameParameter("username")// 设置登录表单中密码字段的参数名,默认为password.passwordParameter("password")// 登录成功后返回的数据.successHandler((res, resp, ex) -> {Object user = ex.getPrincipal();objectMapper.writeValue(resp.getOutputStream(), JsonResponseBody.success(user));}).and()/*配置注销*/.logout()// 设置安全退出的URL路径.logoutUrl("/logout")// 设置退出成功后跳转的路径.logoutSuccessUrl("/").and()/*配置 rememberMe 功能*/.rememberMe()// 指定 rememberMe 的参数名,用于在表单中携带 rememberMe 的值。.rememberMeParameter("remember-me")// 指定 rememberMe 的有效期,单位为秒,默认2周。.tokenValiditySeconds(60)// 指定 rememberMe 的 cookie 名称。.rememberMeCookieName("remember-me-cookie")// 指定 rememberMe 的 token 存储方式,可以使用默认的 PersistentTokenRepository 或自定义的实现。.tokenRepository(persistentTokenRepository())// 指定 rememberMe 的认证方式,需要实现 UserDetailsService 接口,并在其中查询用户信息。.userDetailsService(userDetailsService)return http.build();}
2.4.3CSRF防御(跨站请求伪造) CSRF
(Cross-Site Request Forgery
,跨站请求伪造)是一种利用用户已登录的身份在用户不知情的情况下发送恶意请求的攻击方式。攻击者可以通过构造恶意链接或者伪造表单提交等方式,让用户在不知情的情况下执行某些操作,例如修改密码、转账、发表评论等。
为了防范CSRF
攻击,常见的做法是在请求中添加一个CSRF Token
(也叫做同步令牌、防伪标志),并在服务器端进行验证。CSRF Token
是一个随机生成的字符串,每次请求都会随着请求一起发送到服务器端,服务器端会对这个Token
进行验证,如果Token
不正确,则拒绝执行请求。
在Spring Security
中,防范CSRF
攻击可以通过启用CSRF
保护来实现。启用CSRF
保护后,Spring Security
会自动在每个表单中添加一个隐藏的CSRF Token
字段,并在服务器端进行验证。如果Token
验证失败,则会抛出异常,从而拒绝执行请求。启用CSRF
保护的方式是在Spring Security
配置文件中添加.csrf()
方法,例如:
http .csrf() .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
在上面的配置中,我们使用了CookieCsrfTokenRepository
作为CSRF Token
的存储方式,并设置了httpOnly
为false
,以便在客户端可以访问到该Token
。
在表单中添加:
.csrf()
主要方法介绍:
方法 说明 disable()
关闭CSRF
防御 csrfTokenRepository()
设置CookieCsrfTokenRepository
实例,用于存储和检索CSRF
令牌。与HttpSessionCsrfTokenRepository
不同,CookieCsrfTokenRepository
将CSRF
令牌存储在cookie
中,而不是在会话中。 ignoringAntMatchers()
设置一组Ant模式,用于忽略某些请求的CSRF
保护。例如,如果您想要忽略所有以/api/
开头的请求,可以使用.ignoringAntMatchers("/api/**")
。 csrfTokenManager()
设置CsrfTokenManager
实例,用于管理CSRF
令牌的生成和验证。默认情况下,Spring Security
使用DefaultCsrfTokenManager
实例来生成和验证CSRF
令牌。 requireCsrfProtectionMatcher()
设置RequestMatcher
实例,用于确定哪些请求需要进行CSRF
保护。默认情况下,Spring Security
将对所有非GET、HEAD、OPTIONS和TRACE
请求进行CSRF
保护。
如果针对一些特定的请求接口,不需要进行CSRF
防御,可以通过以下配置忽略:
http.csrf().ignoringAntMatchers("/upload"); // 禁用/upload接口的CSRF防御
三、用户授权 3.1 授权介绍 Spring Security 中的授权分为两种类型:
Spring Security 提供了多种实现授权的机制,最常用的是使用基于注解的方式,建立起访问资源和权限之间的映射关系。
其中最常用的两个注解是 @Secured
和 @PreAuthorize
。@Secured
注解是更早的注解,基于角色的授权比较适用,@PreAuthorize
基于 SpEL
表达式的方式,可灵活定义所需的权限,通常用于基于资源的授权。
3.2构建 UserDetails 对象 3.2.1 准备数据表 sys_user - 用户信息表 sys_role - 角色信息表 sys_user_role - 用户角色表 sys_module - 模块信息表 sys_role_module - 角色权限表
3.2.2 设置用户权限 package com.ycxw.springsecurity.config;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;import com.ycxw.springsecurity.entity.*;import com.ycxw.springsecurity.service.*;import org.springframework.beans.factory.annotation.Autowired;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.Component;import java.util.List;import java.util.Objects;import java.util.stream.Collectors;@Componentpublic class MyUserDetailsService implements UserDetailsService {@Autowiredprivate IUserService userService;@Autowiredprivate IUserRoleService userRoleService;@Autowiredprivate IRoleService roleService;@Autowiredprivate IRoleModuleService roleModuleService;@Autowiredprivate IModuleService moduleService;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {/*查询当前用户*/User user = userService.getOne(new QueryWrapper().eq("username", username));if (user == null) {throw new UsernameNotFoundException("用户名无效");}/* * map 遍历所有的对象,返回新的数据会放到一个新流中 * collect 将流中的元素变成一个集合 * *///先查询出所有的身份idList role_ids = userRoleService.list(new QueryWrapper().eq("user_id", user.getId())).stream().map(UserRole::getRoleId).collect(Collectors.toList());//查询角色对应的权限List roles = roleService.list(new QueryWrapper().in("role_id", role_ids)).stream().map(Role::getRoleName).collect(Collectors.toList());// 查询权限对应的模块List module_ids = roleModuleService.list(new QueryWrapper().in("role_id", role_ids)).stream().map(RoleModule::getModuleId).collect(Collectors.toList());/// 查询模块对应的 URLList modules = moduleService.list(new QueryWrapper().in("id", module_ids)).stream().map(Module::getUrl)/* filter 过滤流中的内容(对象不为空)*/.filter(Objects::nonNull).collect(Collectors.toList());/* * roles -> [管理员,普通用户] * + * modules -> [book:manager:add,book:manager:list] */// 将角色和模块合并为一个集合roles.addAll(modules);// roles [管理员,普通用户,book:manager:add,book:manager:list]// 构建 SimpleGrantedAuthority 对象List authorities = roles.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());// 设置用户的权限user.setAuthorities(authorities);// 返回 UserDetails 对象return user;}}
根据用户名查询用户信息并构建 UserDetails 对象,以便 Spring Security 进行身份验证。
3.3修改SpringSecurity配置类 当我们想要开启spring
方法级安全时,只需要在任何 @Configuration
实例上使用@EnableGlobalMethodSecurity
注解就能达到此目的。同时这个注解为我们提供了prePostEnabled
、securedEnabled
和 jsr250Enabled
三种不同的机制来实现同一种功能。
package com.ycxw.springsecurity.config;import com.fasterxml.jackson.databind.ObjectMapper;import com.ycxw.springsecurity.resp.JsonResponseBody;import com.ycxw.springsecurity.resp.JsonResponseStatus;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.authentication.ProviderManager;import org.springframework.security.authentication.dao.DaoAuthenticationProvider;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.core.userdetails.UserDetailsService;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.web.SecurityFilterChain;import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;import org.springframework.security.web.csrf.CookieCsrfTokenRepository;import org.springframework.security.web.util.matcher.AntPathRequestMatcher;import javax.annotation.Resource;import javax.sql.DataSource;@Configuration@EnableWebSecurity //前置权限验证@EnableGlobalMethodSecurity(prePostEnabled = true) //后置权限验证public class WebSecurityConfig {// 注入数据源(spring自带)@Resourcepublic DataSource dataSource;@Autowiredprivate ObjectMapper objectMapper;/*自定义处理身份验证失败的接口*/@Autowiredprivate MyAuthenticationFailureHandler myAuthenticationFailureHandler;// 注入用户服务@Autowiredprivate UserDetailsService userDetailsService;/* * 密码编码器: 用于对密码进行加密 * */@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}// 创建持久令牌存储库@Beanpublic PersistentTokenRepository persistentTokenRepository() {// 创建一个JdbcTokenRepositoryImpl实例JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();// 设置数据源tokenRepository.setDataSource(dataSource);// 设置启动时创建表tokenRepository.setCreateTableOnStartup(false);// 返回tokenRepositoryreturn tokenRepository;}// 创建认证管理器@Beanpublic AuthenticationManager authenticationManager() throws Exception {// 创建一个DAO认证提供者DaoAuthenticationProvider provider = new DaoAuthenticationProvider();// 设置用户详情服务和密码编码器provider.setUserDetailsService(userDetailsService);provider.setPasswordEncoder(passwordEncoder());// 返回一个提供者管理器return new ProviderManager(provider);}/* * 安全过滤器链 * */@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/").permitAll()//其他所有请求都需要用户进行身份验证。.anyRequest().authenticated().and().formLogin()// 设置登录页面的 URL.loginPage("/")// 设置登录请求的 URL,即表单提交的 URL.loginProcessingUrl("/userLogin")// 设置登录表单中用户名字段的参数名,默认为username.usernameParameter("username")// 设置登录表单中密码字段的参数名,默认为password.passwordParameter("password")// 登录成功后返回的数据.successHandler((res, resp, ex) -> {Object user = ex.getPrincipal();objectMapper.writeValue(resp.getOutputStream(), JsonResponseBody.success(user));})/*登录失败后的处理器*/.failureHandler(myAuthenticationFailureHandler).and().exceptionHandling()//权限不足.accessDeniedHandler((req, resp, ex) -> {objectMapper.writeValue(resp.getOutputStream(), JsonResponseBody.other(JsonResponseStatus.NO_ACCESS));})//没有认证.authenticationEntryPoint((req, resp, ex) -> {objectMapper.writeValue(resp.getOutputStream(), JsonResponseBody.other(JsonResponseStatus.NO_LOGIN));}).and()/*配置注销*/.logout()// 设置安全退出的URL路径.logoutUrl("/logout")// 设置退出成功后跳转的路径.logoutSuccessUrl("/").and()/*配置 rememberMe 功能*/.rememberMe()// 指定 rememberMe 的参数名,用于在表单中携带 rememberMe 的值。.rememberMeParameter("remember-me")// 指定 rememberMe 的有效期,单位为秒,默认2周。.tokenValiditySeconds(60)// 指定 rememberMe 的 cookie 名称。.rememberMeCookieName("remember-me-cookie")// 指定 rememberMe 的 token 存储方式,可以使用默认的 PersistentTokenRepository 或自定义的实现。.tokenRepository(persistentTokenRepository())// 指定 rememberMe 的认证方式,需要实现 UserDetailsService 接口,并在其中查询用户信息。.userDetailsService(userDetailsService).and()/*使用`POST`请求退出登陆,并携带`CRSF`令牌*/.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/").permitAll().and()/*CSRF防御配置*/.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());return http.build();}}
@EnableGlobalMethodSecurity
是Spring Security提供的一个注解,用于启用方法级别的安全性。它可以在任何@Configuration类上使用,以启用Spring Security的方法级别的安全性功能。它接受一个或多个参数,用于指定要使用的安全注解类型和其他选项。以下是一些常用的参数:
prePostEnabled
:如果设置为true
,则启用@PreAuthorize
和@PostAuthorize
注解。默认值为false
。
securedEnabled
:如果设置为true
,则启用@Secured
注解。默认值为false
。
jsr250Enabled
:如果设置为true
,则启用@RolesAllowed
注解。默认值为false
。
proxyTargetClass
:如果设置为true
,则使用CGLIB代理而不是标准的JDK动态代理。默认值为false
。
使用@EnableGlobalMethodSecurity
注解后,可以在应用程序中使用Spring Security提供的各种注解来保护方法,例如@Secured
、@PreAuthorize
、@PostAuthorize
和@RolesAllowed
。这些注解允许您在方法级别上定义安全规则,以控制哪些用户可以访问哪些方法。
注解介绍:
注解 说明 @PreAuthorize
用于在方法执行之前对访问进行权限验证 @PostAuthorize
用于在方法执行之后对返回结果进行权限验证 @Secured
用于在方法执行之前对访问进行权限验证 @RolesAllowed
是Java标准的注解之一,用于在方法执行之前对访问进行权限验证
3.4控制Controller层接口权限 package com.ycxw.springsecurity.controller;import org.springframework.security.access.prepost.PreAuthorize;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;@Controllerpublic class IndexController {@RequestMapping("/")public String toLogin() {return "login";}@RequestMapping("/index")public String toIndex() {return "index";}@ResponseBody@RequestMapping("/order_add")@PreAuthorize("hasAuthority('order:manager:list')") /*设置权限字段*/public String order_add() {return "订单列表";}@ResponseBody@PreAuthorize("hasAuthority('book:manager:add')")@RequestMapping("/book_add")public String book_add() {return "书本新增";}}
3.5 相关页面模版与工具类 1、login.ftl
用户登录 用户:
密码:
记住我
2、自定义数据返回类
例:
JSON 响应的状态码和状态信息:
package com.ycxw.springsecurity.resp;import lombok.Getter;@Getterpublic enum JsonResponseStatus {OK(200, "OK"),UN_KNOWN(500, "未知错误"),RESULT_EMPTY(1000, "查询结果为空"),NO_ACCESS(3001, "没有权限"),NO_LOGIN(4001, "没有登录"),LOGIN_FAILURE(5001, "登录失败"),;private final Integer code;private final String msg;JsonResponseStatus(Integer code, String msg) {this.code = code;this.msg = msg;}}
3、处理身份验证失败封类
package com.ycxw.springsecurity.config;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;import com.fasterxml.jackson.databind.ObjectMapper;import com.ycxw.springsecurity.entity.User;import com.ycxw.springsecurity.resp.JsonResponseBody;import com.ycxw.springsecurity.resp.JsonResponseStatus;import com.ycxw.springsecurity.service.IUserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.core.AuthenticationException;import org.springframework.security.web.authentication.AuthenticationFailureHandler;import org.springframework.stereotype.Component;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;/*实现处理身份验证失败的接口*/@Componentpublic class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {@Autowiredprivate ObjectMapper objectMapper;@Autowiredprivate IUserService userService;@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { /* 利用锁: 判断当前用户登录超过3次进行锁定 */if (1 == 2) {User user = userService.getOne(new QueryWrapper().eq("username", request.getParameter("username")));user.setAccountNonLocked(false);userService.updateById(user);}objectMapper.writeValue(response.getOutputStream(), JsonResponseBody.other(JsonResponseStatus.LOGIN_FAILURE));}}
3.6 权限测试 1、普通用户权限
只拥有两个路径的权限
测试接口:没有book_add权限将不能访问
2、管理员权限
拥有六个路径的权限
测试接口:管理员能访问所有接口