赞
踩
2. 从文档上可以看出来,UsernamePasswordAuthenticationFilter和AuthenticationManager是认证的关键步骤,/login传过来的username和password由UsernamePasswordAuthenticationFilter接收处理成UsernamePasswordAuthenticationToken,再传给AuthenticationManager,在AuthenticationManager中判断密码正确与否
3. 代码实现
(一)、登录部分:登录可以使用两种方式进行登陆
第一种:只用spring security默认提供的/login接口进行登录(这种方式需要修改spring security默认的登录设置,比较办法)
第二种:自己写登陆的controller(这种比较简单就不研究了)
第一步 WebSecurityConfig配置文件
package com.xuhao.springsecuritycom.config; import com.fasterxml.jackson.databind.ObjectMapper; import com.xuhao.springsecuritycom.filters.CustomUsernamePasswordAuthenticationFilter; import com.xuhao.springsecuritycom.filters.TokenFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import java.util.HashMap; import java.util.Map; @Configuration @EnableWebSecurity // 开启注解模式,否则权限注解会无效 @EnableGlobalMethodSecurity( prePostEnabled = true, // 启用@PreAuthorize和@PostAuthorize注解 securedEnabled = true // 启用@Secured注解) ) public class SecurityConfig { @Autowired private TokenFilter tokenFilter; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { httpSecurity .authorizeHttpRequests((requests) -> requests .requestMatchers(HttpMethod.POST,"/user/create", "/login").permitAll() .anyRequest().authenticated() ) .logout((logout) -> logout.permitAll()); // 设置无权访问时的返回 httpSecurity.exceptionHandling().accessDeniedHandler(new CustomAccessDeniedHandler()); // 设置未登录,访问受保护地址的拦截 httpSecurity.exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint()); // 配置token拦截过滤器,这个过滤器很重要,用来判断用户是否登录的逻辑 httpSecurity.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class); // 关闭csrf,否则ajax无法通过拦截 httpSecurity.csrf().disable(); // 不通过Session获取SecurityContext httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); return httpSecurity.build(); } /** * 设置加密规则 * PasswordEncoderFactories.createDelegatingPasswordEncoder() * 创建的DelegatingPasswordEncoder会自动根据配置的密码存储类型来选择合适的加密规则 * */ @Bean public PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } /** * 这是登录过滤器的bean */ @Bean public CustomUsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter() { CustomUsernamePasswordAuthenticationFilter loginFilter = new CustomUsernamePasswordAuthenticationFilter(); loginFilter.setAuthenticationManager(customAuthenticationManager()); return loginFilter; } // 设置判断登录成功的实现类的bean @Bean public CustomAuthenticationManager customAuthenticationManager() { return new CustomAuthenticationManager(); } }
第二步 重写登录接口/login的逻辑
package com.xuhao.springsecuritycom.filters; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.authentication.AuthenticationServiceException; 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 java.io.IOException; import java.util.HashMap; import java.util.Map; /** * 重写登录拦截过滤器 * */ public class CustomUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter { // 接收请求参数 @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (!request.getMethod().equals("POST")) { throw new AuthenticationServiceException( "Authentication method not supported: " + request.getMethod()); } String username = this.obtainUsername(request); username = username != null ? username.trim() : ""; String password = this.obtainPassword(request); password = password != null ? password : ""; UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password); this.setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } // 设置/login接口登录成功以后得接口返回 @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { Map<String,Object> map = new HashMap<>(); map.put("code",200); map.put("msg","登陆成功"); // 获取用户信息这里也可以把生成的token返回给前端,写死token懒得接jwt了,后面就判断token=1就是登录了 map.put("token","1"); map.put("authentication",authResult); String string = new ObjectMapper().writeValueAsString(map); response.setContentType("application/json;charset=UTF-8"); response.getWriter().println(string); } // 设置/login接口登录失败以后得接口返回 @Override protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { Map<String,Object> map = new HashMap<>(); map.put("code",302); map.put("msg","登陆失败"); String string = new ObjectMapper().writeValueAsString(map); response.setContentType("application/json;charset=UTF-8"); response.getWriter().println(string); } }
第三步 重写AuthenticationManager,用于判断密码是否正确
package com.xuhao.springsecuritycom.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.crypto.password.PasswordEncoder; import java.util.ArrayList; import java.util.Collection; public class CustomAuthenticationManager implements AuthenticationManager { @Autowired PasswordEncoder passwordEncoder; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { // 从传入的 Authentication 对象中获取用户提供的凭证信息,比如用户名和密码 String username = authentication.getName(); String password = authentication.getCredentials().toString(); // 写死密码123456用户名xuhao,开发时应该是从数据库获取到的密码 String contrastPaw = passwordEncoder.encode("123456"); // 模拟一个权限列表 Collection<SimpleGrantedAuthority> authorities = new ArrayList<>(); authorities.add(new SimpleGrantedAuthority("ROLE_ADD")); if(username.equals("xuhao") && passwordEncoder.matches(password, contrastPaw)) { return new UsernamePasswordAuthenticationToken(username, password, authorities); } else { throw new BadCredentialsException("Invalid username or password"); } } }
第四步 重写未登录,未授权的异常返回
package com.xuhao.springsecuritycom.config; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; import java.io.IOException; import java.util.HashMap; import java.util.Map; /** * 未授权访问拦截 * */ public class CustomAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { Map<String,Object> map = new HashMap<>(); map.put("code",403); map.put("msg","您无权访问此地址"); String string = new ObjectMapper().writeValueAsString(map); response.setContentType("application/json;charset=UTF-8"); response.getWriter().println(string); } }
package com.xuhao.springsecuritycom.config; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import java.io.IOException; import java.util.HashMap; import java.util.Map; /** * 未登录返回拦截 * */ public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { Map<String,Object> map = new HashMap<>(); map.put("code",401); map.put("msg","请登录后访问"); String string = new ObjectMapper().writeValueAsString(map); response.setContentType("application/json;charset=UTF-8"); response.getWriter().println(string); } }
第五步 写测试接口
package com.xuhao.springsecuritycom.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.access.annotation.Secured; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.Map; @RestController @RequestMapping("/user") public class User { @Autowired PasswordEncoder encoder; @PostMapping("/create") public ResponseEntity<?> createUser(@RequestParam String userName, @RequestParam String password) { Map<String, String> map = new HashMap<>(); map.put("userName", userName); map.put("password", encoder.encode(password)); System.out.println(encoder.matches("123456", encoder.encode(password))); return ResponseEntity.ok().body(map); } @GetMapping("/see") // 测试测试权限,全局写死权限为ROLE_ADD,所以访问SEE时就会被权限过滤器拦截 @Secured("ROLE_SEE") public ResponseEntity<?> seeUser() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); return ResponseEntity.ok().body(authentication); } }
附上源码地址: spring security6示例代码
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。