当前位置: 首页 > news >正文

Spring Boot + Spring Security基础入门教程

Spring Security简介

Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架。Spring Security 致力于为 Java 应用程序提供身份验证和授权的能力。

Spring Security 两大重要核心功能:用户认证(Authentication)用户授权(Authorization)

用户认证:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。

用户授权:验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,有的用户既能读取,又能修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。

准备工作

创建Spring Boot项目

pom.xml文件(根据自己所需引入)

    <dependencies><!-- security(安全认证) --><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-plus(数据库操作) --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.2</version></dependency><!-- redis(缓存) --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- swagger(api接口文档) --><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.9.2</version></dependency><!-- jjwt(token生成与校验) --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency><!-- fastjson2(JSON处理) --><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>2.0.23</version></dependency><!-- mysql(连接驱动) --><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><!-- druid(mysql连接池) --><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.16</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-test</artifactId><scope>test</scope></dependency></dependencies>

认证(Authentication)

登陆校验流程

原理初探

SpringSecurity 的原理其实就是一个过滤器链,内部包含了提供各种功能的过滤器。这里我们可以看看入门案例中的过滤器。

图中只展示了核心过滤器,其它的非核心过滤器并没有在图中展示。

UsernamePasswordAuthenticationFilter:负责处理我们在登陆页面填写了用户名密码后的登陆请求。入门案例的认证工作主要有它负责。

ExceptionTranslationFilter:处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException 。

FilterSecurityInterceptor:负责权限校验的过滤器。

我们可以通过Debug查看当前系统中SpringSecurity过滤器链中有哪些过滤器及它们的顺序。

在控制台处点击Evaluate Expression或Alt+F8,如下图:

 然后输入 run.getBean(DefaultSecurityFilterChain.class) 进行过滤,可以看到 run 容器中的 15 个过滤器:

Spring Security配置类

import com.zm.springsecurity.common.filter.CustomAuthenticationFilter;
import com.zm.springsecurity.common.security.CustomAuthenticationFailureHandler;
import com.zm.springsecurity.common.security.CustomAuthenticationSuccessHandler;
import com.zm.springsecurity.common.security.CustomLogoutSuccessHandler;
import com.zm.springsecurity.service.impl.CustomUserDetailsServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启方法级安全
public class SecurityConfig extends WebSecurityConfigurerAdapter {private static final String URL_WHITELIST[] ={"/v2/api-docs", "/swagger-resources/configuration/ui","/swagger-resources", "/swagger-resources/configuration/security","/swagger-ui.html", "/webjars/**", // swagger不需要授权即可访问的路径"/login","/logout","/my/login","/my/logout","/captcha","/password","/image/**","/test/**"};@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}@Beanprotected CustomAuthenticationFilter customAuthenticationFilter() throws Exception {CustomAuthenticationFilter authenticationFilter = new CustomAuthenticationFilter();authenticationFilter.setFilterProcessesUrl("/my/login");authenticationFilter.setUsernameParameter("username");authenticationFilter.setPasswordParameter("password");authenticationFilter.setAuthenticationManager(super.authenticationManager());authenticationFilter.setAuthenticationSuccessHandler(new CustomAuthenticationSuccessHandler());authenticationFilter.setAuthenticationFailureHandler(new CustomAuthenticationFailureHandler());return authenticationFilter;}//    @Override
//    @Bean
//    public AuthenticationManager authenticationManagerBean() throws Exception {
//        return super.authenticationManagerBean();
//    }
//
//    @Override
//    protected AuthenticationManager authenticationManager() throws Exception {
//        return super.authenticationManager();
//    }@Overrideprotected void configure(HttpSecurity http) throws Exception {http.cors().and().csrf().disable() // 开启跨域请求和关闭csrf攻击.userDetailsService(new CustomUserDetailsServiceImpl())
//                .formLogin().loginPage("/login_page")
//                .loginProcessingUrl("/my/login")
//                .usernameParameter("username").passwordParameter("password").permitAll()
//                .successHandler(new CustomAuthenticationSuccessHandler()) // 认证成功处理器
//                .failureHandler(new CustomAuthenticationFailureHandler()) // 认证失败处理器
//                .and().logout().logoutUrl("/my/logout").logoutSuccessHandler(new CustomLogoutSuccessHandler()) // 退出登录成功处理器.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // session禁用配置(无状态).and().authorizeRequests()  // 验证请求拦截规则.antMatchers(URL_WHITELIST).permitAll() // 配置访问认证白名单.antMatchers("/admin/**").hasRole("admin") // 要具有某种权限.antMatchers("/user/**").hasAnyRole("admin", "user") // 要具有某种权限中的一种.anyRequest().authenticated();http.addFilterAt(this.customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);}
}

使用数据库进行认证

注:本文采用 MyBatis-Plus 作为持久层框架,与 MyBatis-Plus 相关内容自行编写。

实现UserDetails接口

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;public class CustomUserDetails implements UserDetails {private User user;private List<SimpleGrantedAuthority> authorityList;public CustomUserDetails() {}public CustomUserDetails(User user, List<String> roleList) {this.user = user;this.authorityList = roleList.stream().map(role -> new SimpleGrantedAuthority(role)).collect(Collectors.toList());}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return this.authorityList;}@Overridepublic String getPassword() {return user.getPassword();}@Overridepublic String getUsername() {return user.getPassword();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return user.getStatus();}
}

自定义UsernamePasswordAuthenticationFilter过滤器

若处理请求为表单类型的数据,则此步忽略并删除 Security 配置类中 CustomAuthenticationFilter 相关的内容。UsernamePasswordAuthenticationFilter 为认证过滤器,默认只能处理表单提交的数据,如需处理 JSON 数据,则需要重写 UsernamePasswordAuthenticationFilter 的 attemptAuthentication() 方法。

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;/*** 登录认证过滤器,处理认证的请求体为 JSON 的数据*/
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {String contentType = request.getContentType();logger.info("contentType = " + contentType);if (!Objects.isNull(contentType) && (contentType.equals(MediaType.APPLICATION_JSON_VALUE) || contentType.equals(MediaType.APPLICATION_JSON_UTF8_VALUE))) {UsernamePasswordAuthenticationToken authRequest = null;try (InputStream inputStream = request.getInputStream()) {ObjectMapper mapper = new ObjectMapper(); // JSON数据映射器Map<String,String> params = mapper.readValue(inputStream, Map.class);authRequest = new UsernamePasswordAuthenticationToken(params.get("username"), params.get("password"));} catch (IOException e) {e.printStackTrace();authRequest = new UsernamePasswordAuthenticationToken("", "");} finally {setDetails(request, authRequest);return this.getAuthenticationManager().authenticate(authRequest);}}else {return super.attemptAuthentication(request, response);}}
}

自定义处理器

JWT工具类

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.util.StringUtils;import java.util.Date;public class JWTUtils {private static final String tokenSignKey = "zm_sign_key"; // 私钥(盐),太短会报异常:secret key byte array cannot be null or empty.private static final Integer tokenExpiration = 60 * 60 * 24 * 14; // 14天public static String createToken(String username){String token = Jwts.builder().setSubject("AUTH-USER").setExpiration(new Date(System.currentTimeMillis() + tokenExpiration)).claim("username", username).signWith(SignatureAlgorithm.HS512, tokenSignKey).compact();return token;}public static String createToken(Long userId, String username){String token = Jwts.builder().setSubject("AUTH-USER").setExpiration(new Date(System.currentTimeMillis() + tokenExpiration)).claim("userId", userId).claim("username", username).signWith(SignatureAlgorithm.HS512, tokenSignKey).compact();return token;}public static Long getUserId(String token) {try {if (!StringUtils.hasLength(token)) {return null;}Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);Claims claims = claimsJws.getBody();return claims.get("userId", Long.class);} catch (Exception e) {e.printStackTrace();return null;}}public static String getUsername(String token) {try {if (!StringUtils.hasLength(token)) {return "";}Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);Claims claims = claimsJws.getBody();return claims.get("username", String.class);} catch (Exception e) {e.printStackTrace();return null;}}
}

响应JSON数据信息

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;public class ResponseUtils {public static void response(HttpServletResponse response, String data) throws IOException {response.setContentType("text/html;charset=utf-8");PrintWriter responseWriter = response.getWriter();responseWriter.write(data);responseWriter.flush();responseWriter.close();}
}

自定义认证成功处理器

import com.alibaba.fastjson2.JSON;
import com.zm.springsecurity.utils.JWTUtils;
import com.zm.springsecurity.utils.ResponseUtils;
import com.zm.springsecurity.utils.ResultUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {String customUserDetails = authentication.getPrincipal().toString();  // 继承UserDetails的对象String token = JWTUtils.createToken(username);String jsonString = JSON.toJSONString(ResultUtils.ok("登录成功", token));ResponseUtils.response(response, jsonString);}
}

为什么 authentication.getPrincipal() 的结果是继承 UserDetails 的对象:ProviderManager 中的 this.copyDetails(authentication, result); 语句。

    private void copyDetails(Authentication source, Authentication dest) {if (dest instanceof AbstractAuthenticationToken && dest.getDetails() == null) {AbstractAuthenticationToken token = (AbstractAuthenticationToken)dest;token.setDetails(source.getDetails());}}

自定义认证失败处理器

import com.alibaba.fastjson2.JSON;
import com.zm.springsecurity.utils.ResponseUtils;
import com.zm.springsecurity.utils.ResultUtils;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {String message = exception.getMessage();if(exception instanceof BadCredentialsException){message = "用户名或密码错误!";}String jsonString = JSON.toJSONString(ResultUtils.fail(message));ResponseUtils.response(response, jsonString);}
}

自定义注销成功处理器

import com.alibaba.fastjson2.JSON;
import com.zm.springsecurity.utils.ResponseUtils;
import com.zm.springsecurity.utils.ResultUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class CustomLogoutSuccessHandler implements LogoutSuccessHandler {@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {String jsonString = JSON.toJSONString(ResultUtils.ok("退出登录成功!"));ResponseUtils.response(response, jsonString);}
}

授权(Authorization

未完待续... 

http://www.lryc.cn/news/60470.html

相关文章:

  • MySQL数据库,表的增删改查详细讲解
  • SpringCloud-Gateway实现网关
  • Redis 如何配置读写分离架构(主从复制)?
  • 代码随想录二刷day05 | 哈希表之242.有效的字母异位词 349. 两个数组的交集 202. 快乐数 1. 两数之和
  • 2023年4月广东省计算机软考中/高级备考班招生简章
  • 在Github中77k星的王炸AutoGPT,会独立思考,直接释放双手
  • FVM链的Themis Pro,5日ido超百万美元
  • OpenCV实战——尺度不变特征检测器
  • 如何快速建立一个podman环境
  • 计算机视觉:人工智能领域当下火热的计算机视觉技术综述
  • EMC 专用名词大全~骚扰波形
  • 14:24面试,14:32就出来了 ,问的实在是太...
  • 高频算法题
  • AI工程师眼中的未来 | 年轻人如何求职选方向
  • 能自动翻译的软件-最精准的翻译软件
  • 7.1 大学排行榜分析(project)
  • TensorFlow 2.0 的新增功能:第三、四部分
  • 第1章 如何听起来像数据科学家
  • 哈希表题目:在系统中查找重复文件
  • 机器人感知与控制关键技术及其智能制造应用
  • 精通线程池,看这一篇就够了
  • 解决图片、视频地址加密问题
  • GPT引领学习之旅:一篇让程序员轻松掌握Elasticsearch的攻略
  • 23种设计模式-仲裁者模式(Android应用场景介绍)
  • 【数据统计】— 极大似然估计 MLE、最大后验估计 MAP、贝叶斯估计
  • Zookeeper学习笔记
  • go语言切片做函数参数传递+append()函数扩容
  • 2023.04.16 学习周报
  • 【面试】如何设计SaaS产品的数据权限?
  • ansible管理变量