Springboot整合SpringSecurity的完整案例详解
作者:kangkang-
一.Spring Security介绍
Spring Security是基于Spring生态圈的,用于提供安全访问控制解决方案的框架。Spring Security的安 全管理有两个重要概念,分别是Authentication(认证)和Authorization(授权)。 为了方便Spring Boot项目的安全管理,Spring Boot对Spring Security安全框架进行了整合支持,并提 供了通用的自动化配置,从而实现了Spring Security安全框架中包含的多数安全管理功能。
Spring Security登录认证主要涉及两个重要的接口 UserDetailService和UserDetails接口。
UserDetailService接口主要定义了一个方法 loadUserByUsername(String username)用于完成用户信息的查 询,其中username就是登录时的登录名称,登录认证时,需要自定义一个实现类实现UserDetailService接 口,完成数据库查询,该接口返回UserDetail。
UserDetail主要用于封装认证成功时的用户信息,即UserDetailService返回的用户信息,可以用Spring
自己的User对象,但是最好是实现UserDetail接口,自定义用户对象。
二.Spring Security认证步骤
1. 自定UserDetails类:当实体对象字段不满足时需要自定义UserDetails,一般都要自定义
UserDetails。
2. 自定义UserDetailsService类,主要用于从数据库查询用户信息。
3. 创建登录认证成功处理器,认证成功后需要返回JSON数据,菜单权限等。
4. 创建登录认证失败处理器,认证失败需要返回JSON数据,给前端判断。
5. 创建匿名用户访问无权限资源时处理器,匿名用户访问时,需要提示JSON。
6. 创建认证过的用户访问无权限资源时的处理器,无权限访问时,需要提示JSON。
7. 配置Spring Security配置类,把上面自定义的处理器交给Spring Security。
三.Spring Security认证实现
3.1添加Spring Security依赖
在pom.xml文件中添加Spring Security核心依赖,代码如下所
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
3.2自定义UserDetails
当实体对象字段不满足时Spring Security认证时,需要自定义UserDetails。
1. 将User类实现UserDetails接口
2. 将原有的isAccountNonExpired、isAccountNonLocked、isCredentialsNonExpired和isEnabled属性修 改成boolean类型,同时添加authorities属性。
@Data @TableName("sys_user") public class User implements Serializable, UserDetails { //省略原有的属性...... /** * 帐户是否过期(1 未过期,0已过期) */ private boolean isAccountNonExpired = true; /** * 帐户是否被锁定(1 未过期,0已过期) */ private boolean isAccountNonLocked = true; /** * 密码是否过期(1 未过期,0已过期) */ private boolean isCredentialsNonExpired = true; /** * 帐户是否可用(1 可用,0 删除用户) */ private boolean isEnabled = true; /** * 权限列表 */ @TableField(exist = false) Collection<? extends GrantedAuthority> authorities;
3.3.编写Service接口
public interface UserService extends IService<User> { /** * 根据用户名查询用户信息 * @param userName * @return */ User findUserByUserName(String userName); }
3.4.编写ServiceImpl
package com.manong.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.manong.entity.User; import com.manong.dao.UserMapper; import com.manong.service.UserService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** * <p> * 服务实现类 * </p> * * @author lemon * @since 2022-12-06 */ @Service @Transactional public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { @Override public User findUserByUserName(String username) { //创建条件构造器对象 QueryWrapper queryWrapper=new QueryWrapper(); queryWrapper.eq("username",username); //执行查询 return baseMapper.selectOne(queryWrapper); } }
3.5. 自定义UserDetailsService类
package com.manong.config.security.service; import com.manong.entity.Permission; import com.manong.entity.User; import com.manong.service.PermissionService; import com.manong.service.UserService; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; 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 javax.annotation.Resource; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; /* * 用户认证处理器类 * */ @Component public class CustomerUserDetailService implements UserDetailsService { @Resource private UserService userService; @Resource private PermissionService permissionService; public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{ //根据对象查找用户信息 User user = userService.findUserByUserName(username); //判断对象是否为空 if(user==null){ throw new UsernameNotFoundException("用户的账号密码错误"); } //查询当前登录用户拥有权限列表 List<Permission> permissionList = permissionService.findPermissionListByUserId(user.getId()); //获取对应的权限编码 List<String> codeList = permissionList.stream() .filter(Objects::nonNull) .map(item -> item.getCode()) .filter(Objects::nonNull) .collect(Collectors.toList()); //将权限编码转换成数据 String [] strings=codeList.toArray(new String[codeList.size()]); //设置权限列表 List<GrantedAuthority> authorityList = AuthorityUtils.createAuthorityList(strings); //将权限列表设置给User user.setAuthorities(authorityList); //设置该用户拥有的菜单 user.setPermissionList(permissionList); //查询成功 return user; } }
四.通常情况下,我们需要自定义四个类来获取处理类 包括成功,失败,匿名用户,登录了但没有权限的用户
4.1.成功
package com.manong.config.security.handler; import com.alibaba.fastjson.serializer.SerializerFeature; import com.manong.entity.User; import com.manong.utils.JwtUtils; import com.manong.utils.LoginResult; import com.manong.utils.ResultCode; import io.jsonwebtoken.Jwts; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; import sun.net.www.protocol.http.AuthenticationHeader; import javax.annotation.Resource; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; import com.alibaba.fastjson.JSON; /* * 登录认证成功处理器类 * */ @Component public class LoginSuccessHandler implements AuthenticationSuccessHandler { @Resource private JwtUtils jwtUtils; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { //设置相应编码格式 response.setContentType("application/json;charset-utf-8"); //获取当前登录用户的信息 User user = (User) authentication.getPrincipal(); //创建token对象 String token = jwtUtils.generateToken(user); //设置token的秘钥和过期时间 long expireTime = Jwts.parser() .setSigningKey(jwtUtils.getSecret()) .parseClaimsJws(token.replace("jwt_", "")) .getBody().getExpiration().getTime();//设置过期时间 //创建LOgin登录对象 LoginResult loginResult=new LoginResult(user.getId(), ResultCode.SUCCESS,token,expireTime); //将对象转换成json格式 //消除循环引用 String result = JSON.toJSONString(loginResult, SerializerFeature.DisableCircularReferenceDetect); //获取输出流 ServletOutputStream outputStream = response.getOutputStream(); outputStream.write(result.getBytes(StandardCharsets.UTF_8)); outputStream.flush(); outputStream.close(); } }
4.2 失败
package com.manong.config.security.handler; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature; import com.baomidou.mybatisplus.extension.api.R; import com.manong.entity.User; import com.manong.utils.Result; import com.manong.utils.ResultCode; import org.springframework.security.authentication.*; 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.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; @Component public class LoginFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { //设置相应编码格式 response.setContentType("application/json;charset-utf-8"); //获取输出流 ServletOutputStream outputStream = response.getOutputStream(); //定义变量,保存异常信息 String message=null; //判断异常类型 if(exception instanceof AccountExpiredException){ message="账户过期失败"; } else if(exception instanceof BadCredentialsException){ message="用户名的账号密码错误,登录失败"; } else if(exception instanceof CredentialsExpiredException){ message="密码过期,登录失败"; } else if(exception instanceof DisabledException){ message="账号过期,登录失败"; } else if(exception instanceof LockedException){ message="账号被上锁,登录失败"; } else if(exception instanceof InternalAuthenticationServiceException){ message="用户不存在"; } else { message="登录失败"; } //将结果转换为Json格式 String result = JSON.toJSONString(Result.error().code(ResultCode.ERROR).message(message)); //将结果保存到输出中 outputStream.write(result.getBytes(StandardCharsets.UTF_8)); outputStream.flush(); outputStream.close(); } }
4.3 匿名无用户
package com.manong.config.security.handler; import com.alibaba.fastjson.serializer.SerializerFeature; import com.manong.entity.User; import com.manong.utils.Result; import com.manong.utils.ResultCode; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; import sun.net.www.protocol.http.AuthenticationHeader; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; import com.alibaba.fastjson.JSON; /* * 匿名访问无权限资源处理器 * */ @Component public class AnonymousAuthenticationHandler implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { response.setContentType("application/json;charset-utf-8"); //获取输出流 ServletOutputStream outputStream = response.getOutputStream(); //将对象转换成json格式 //消除循环引用 String result = JSON.toJSONString(Result.error().code(ResultCode.NO_AUTH).message("匿名用户无权限访问"), SerializerFeature.DisableCircularReferenceDetect); //获取输出流 outputStream.write(result.getBytes(StandardCharsets.UTF_8)); outputStream.flush(); outputStream.close(); } }
4.4 登录了但是没有权限的用户
package com.manong.config.security.handler; import com.alibaba.fastjson.serializer.SerializerFeature; import com.manong.entity.User; import com.manong.utils.Result; import com.manong.utils.ResultCode; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.core.Authentication; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; import sun.net.www.protocol.http.AuthenticationHeader; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; import com.alibaba.fastjson.JSON; /* * 认证用户访问无权限资源处理器 * */ @Component public class CustomerAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { response.setContentType("application/json;charset-utf-8"); ServletOutputStream outputStream = response.getOutputStream(); //将对象转换成json格式 //消除循环引用 String result = JSON.toJSONString(Result.error().code(ResultCode.NO_AUTH).message("用户无权限访问,请联系教务处"), SerializerFeature.DisableCircularReferenceDetect); //获取输出流 outputStream.write(result.getBytes(StandardCharsets.UTF_8)); outputStream.flush(); outputStream.close(); } }
五.编写SpringSecurity配置类,把上面的四个类进行合并
package com.manong.config.security.service; import com.manong.config.security.handler.AnonymousAuthenticationHandler; import com.manong.config.security.handler.CustomerAccessDeniedHandler; import com.manong.config.security.handler.LoginFailureHandler; import com.manong.config.security.handler.LoginSuccessHandler; import org.springframework.context.annotation.Bean; 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.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Component; import javax.annotation.Resource; @Component @EnableWebSecurity public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { @Resource private LoginSuccessHandler loginSuccessHandler; @Resource private LoginFailureHandler loginFailureHandler; @Resource private CustomerAccessDeniedHandler customerAccessDeniedHandler; @Resource private AnonymousAuthenticationHandler anonymousAuthenticationHandler; @Resource private CustomerUserDetailService customerUserDetailService; //注入加密类 @Bean public BCryptPasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } //处理登录认证 @Override protected void configure(HttpSecurity http) throws Exception { //登录过程处理 http.formLogin() //表单登录 .loginProcessingUrl("/api/user/login") //登录请求url地址 .successHandler(loginSuccessHandler) //认证成功 .failureHandler(loginFailureHandler) //认证失败 .and() .csrf().disable() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) //不创建Session .and().authorizeRequests() //设置需要拦截的请求 .antMatchers("/api/user/login").permitAll()//登录放行 .anyRequest().authenticated() //其他请求一律拦截 .and() .exceptionHandling() .authenticationEntryPoint(anonymousAuthenticationHandler) //匿名无权限类 .accessDeniedHandler(customerAccessDeniedHandler) //认证用户无权限 .and() .cors();//支持跨域 } //认证配置处理器 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(customerUserDetailService) .passwordEncoder(this.passwordEncoder());//密码加密 } }
到此这篇关于Springboot整合SpringSecurity的文章就介绍到这了,更多相关Springboot整合SpringSecurity内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
您可能感兴趣的文章:
- SpringBoot启动security后如何关闭弹出的/login页面
- SpringBoot整合Spring Security构建安全的Web应用
- SpringBoot整合新版SpringSecurity完整过程
- SpringBoot集成Swagger使用SpringSecurity控制访问权限问题
- SpringBoot集成SpringSecurity安全框架方式
- SpringSecurity在SpringBoot中的自动装配过程
- Springbootadmin与security冲突问题及解决
- SpringBoot整合Springsecurity实现数据库登录及权限控制功能
- SpringBoot配置Spring Security的实现示例