一、springsecurity自定义过滤url配置
package com.school.information.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 需要放行的请求
*/
@Data
@Component
@ConfigurationProperties(prefix = "ignore")
public class IgnoreRequestConfig {
private List url;
}
package com.school.information.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class CustomRequestMatcherConfig {
@Autowired
private IgnoreRequestConfig ignoreRequestConfig;
@Bean
public RequestMatcher[] requestMatchers() {
List requestMatcherList = new ArrayList<>();
ignoreRequestConfig.getUrl().forEach(path -> requestMatcherList.add(new AntPathRequestMatcher(path)));
return requestMatcherList.toArray(new RequestMatcher[0]);
}
}
二、springsecurity配置
package com.school.information.config;
import com.school.information.core.security.filter.JwtAuthenticationTokenFilter;
import com.school.information.core.security.handler.AccessDeniedHandlerImpl;
import com.school.information.core.security.handler.AuthenticationEntryPointImpl;
import com.school.information.core.security.provider.WechatAppUserAuthenticationProvider;
import com.school.information.core.service.CustomPasswordService;
import com.school.information.core.service.SecurityUserServiceImpl;
import com.school.information.core.service.WechatAppUserServiceImpl;
import lombok.RequiredArgsConstructor;
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.EnableMethodSecurity;
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.WebSecurityCustomizer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.RequestMatcher;
import java.util.Arrays;
/*
* SecuritConfig配置类
*/
@Configuration
@EnableWebSecurity
@EnableMethodSecurity // 开启注解授权功能
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
private final SecurityUserServiceImpl userDetailsService;
private final WechatAppUserServiceImpl wechatAppUserService;
/**
* 认证失败处理器
*/
private final AuthenticationEntryPointImpl authenticationEntryPoint;
private final AccessDeniedHandlerImpl accessDeniedHandler;
private final RequestMatcher[] requestMatchers;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// 关闭csrf 因为不使用session
http.csrf(csrf -> csrf.disable());
// 禁用HTTP响应标头
http.headers(headers -> headers.frameOptions(frameOptionsConfig -> frameOptionsConfig.disable()));
//不通过Session获取SecurityContext 基于token,所以不需要session
http.sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
// 过滤请求
http.authorizeHttpRequests(authorize -> authorize
.requestMatchers(requestMatchers).permitAll()
.anyRequest().authenticated());
//对于登录login 注册register 验证码captchaImage 无需拦截 直接访问
// .antMatchers("/", "/token/captcha").permitAll()
// // 静态资源,可匿名访问
// .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
// .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
// 除上面外的所有请求全部需要鉴权认证
// .anyRequest().authenticated();
// 添加JWT filter
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// 认证失败处理类
http.exceptionHandling(
exceptionHandlingConfigurer -> exceptionHandlingConfigurer
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler)
);
// SpringSecurity设置允许跨域
http.cors(cors -> cors.disable());
return http.build();
}
/**
* 静态文件放行
*/
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().requestMatchers("/staic/**", "/web/**");
}
@Bean
public PasswordEncoder passwordEncoder() {
return new CustomPasswordService();
}
/**
* 设置默认认证提供 用户名密码登录
*/
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
final DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
/**
* 设置小程序的登录验证方式 openid验证登录 没有密码
*
* @return
*/
@Bean
public WechatAppUserAuthenticationProvider daoWechatAppUserAuthenticationProvider() {
final WechatAppUserAuthenticationProvider wechatAppUserAuthenticationProvider = new WechatAppUserAuthenticationProvider();
wechatAppUserAuthenticationProvider.setUserDetailsService(wechatAppUserService);
return wechatAppUserAuthenticationProvider;
}
// 获取AuthenticationManager(认证管理器),登录时认证使用。 默认UsernamePasswordAuthenticationToken
// @Bean
// public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
// return authenticationConfiguration.getAuthenticationManager();
// }
/**
* 如果项目中需要多个不同的认证管理器,需要使用下方的代码,将不同的认证管理器交由providerManager去管理
*
* @return
* @throws Exception
*/
@Bean
public AuthenticationManager authenticationManager() throws Exception {
ProviderManager authenticationManager = new ProviderManager(
Arrays.asList(daoAuthenticationProvider(), daoWechatAppUserAuthenticationProvider())
);
return authenticationManager;
}
}
三、登录验证controller
package com.school.information.controller;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.school.information.core.constant.TokenConstant;
import com.school.information.core.exception.ServiceException;
import com.school.information.core.security.authen.WechatAppAuthenticationToken;
import com.school.information.core.security.entity.SecurityUser;
import com.school.information.core.security.entity.WechatAppUser;
import com.school.information.core.service.RedisService;
import com.school.information.core.wechat.constant.WeChatConstant;
import com.school.information.core.wechat.properties.WeChatAppProperties;
import com.school.information.entity.SysUserEntity;
import com.school.information.entity.SysWechatUserEntity;
import com.school.information.enums.result.SysResultEnum;
import com.school.information.service.SysWechatUserService;
import com.school.information.utils.JwtUtil;
import com.school.information.utils.RSAUtil;
import com.school.information.utils.RandomIdUtil;
import com.school.information.utils.ResultUtil;
import com.school.information.vo.LoginVO;
import com.school.information.vo.TokenVO;
import com.wf.captcha.SpecCaptcha;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* 用户登录token处理,及注销
*
* @author yuchuanchuan
* @date 2020-12-07
*/
@Slf4j
@RestController
@RequestMapping("token")
@RequiredArgsConstructor
public class TokenController {
private final WeChatAppProperties weChatAppProperties;
private final RedisService redisService;
private final AuthenticationManager authenticationManager;
private final SysWechatUserService sysWechatUserService;
@GetMapping("/test")
public String test() {
return "ceshi";
}
/**
* 生成验证码
*
* @param request
* @param response
* @throws Exception
*/
@GetMapping("/captcha")
public ResultUtil captcha(@RequestParam("code") String code, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 防止重复刷验证码,导致redis缓存过多。同时也防止没有过期的旧的验证码进行验证
if (StringUtils.isNotBlank(code)) {
// 判断redis中是否存在 验证码
if (redisService.hasKey(code)) {
// 存在则在redis中移除
redisService.del(code);
}
}
// ArithmeticCaptcha arithmeticCaptcha = new ArithmeticCaptcha(110, 38, 2);
// String captchaValue = arithmeticCaptcha.text();
SpecCaptcha captcha = new SpecCaptcha(110, 38, 4);
String captchaValue = captcha.text();// 获取验证码的字符
String uuid = RandomIdUtil.getUUID();
redisService.set(uuid, captchaValue, TokenConstant.CODE_EXPIRES_IN);
// 验证码信息
Map imgResult = new HashMap(2) {{
put("img", captcha.toBase64());
put("uuid", uuid);
}};
return ResultUtil.success(imgResult);
}
/**
* 后台登录功能
*
* @return
*/
@PostMapping("/login")
public ResultUtil login(@Valid @RequestBody LoginVO loginVO) throws Exception {
log.info("===============进入pc端后端管理认证 用户名和密码的登录方式============");
log.info("##loginVO={}", loginVO);
if (Objects.isNull(loginVO)) {
log.error("参数错误");
return ResultUtil.ERROR_ARG;
}
if (StringUtils.isBlank(loginVO.getUuid()) || !redisService.hasKey(loginVO.getUuid())) {
log.error("验证码不存在");
return ResultUtil.error(SysResultEnum.INVALID_CAPTCHA);
}
// 验证验证码是否正确
if (!loginVO.getCode().trim().equalsIgnoreCase((String) redisService.get(loginVO.getUuid()))) {
log.error("验证码错误");
return ResultUtil.error(SysResultEnum.ERROR_CAPTCHA);
}
Authentication authenticate = null;
try {
// 通过UsernamePasswordAuthenticationToken获取用户名和密码
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
loginVO.getUserName(), RSAUtil.decrypt(loginVO.getPassword()));
// AuthenticationManager委托机制对authenticationToken 进行用户认证 去调用UserDetailsServiceImpl.loadUserByUsername
authenticate = authenticationManager.authenticate(authenticationToken);
} catch (Exception e) {
log.error("##登录认证失败:{}", e.getMessage());
throw new ServiceException(SysResultEnum.USER_NAME_PASSWORD_ERROR);
} finally {
SecurityContextHolder.clearContext();
}
// 如果认证通过,使用user生成jwt jwt存入ResultUtil 返回
TokenVO tokenVO = new TokenVO();
// 如果认证通过,拿到这个当前登录用户信息
SecurityUser securityUser = (SecurityUser) authenticate.getPrincipal();
// 获取当前用户的userid
SysUserEntity sysUser = securityUser.getSysUser();
Map map = new HashMap<>(6);
map.put(TokenConstant.LOGIN_DEVICE, TokenConstant.LOGIN_DEVICE_PC);
map.put(TokenConstant.JWT_USER_ID, sysUser.getId().toString());
map.put(TokenConstant.JWT_USER_PHONE, sysUser.getPhone());
map.put(TokenConstant.JWT_USER_NAME, sysUser.getRealName());
map.put(TokenConstant.JWT_USER_PERMS, securityUser.getPermissions());
map.put(TokenConstant.JWT_USER_ROLES, securityUser.getRoles());
String access_token = JwtUtil.createJwt(map, TokenConstant.EXPIRES_IN);
String refresh_token = JwtUtil.createJwt(map, TokenConstant.REFRESH_EXPIRES_IN);
// 把完整的用户信息存入redis userid为key 用户信息为value
redisService.set(TokenConstant.LOGIN_USER_REDIS_KEY + TokenConstant.LOGIN_DEVICE_PC + "_" + sysUser.getId() + "_" + sysUser.getPhone(), securityUser, TokenConstant.EXPIRES_IN);
tokenVO.setAccess_token(access_token).setRefresh_token(refresh_token).setExpires_in(TokenConstant.EXPIRES_IN);
return ResultUtil.success(tokenVO);
}
/**
* 退出 注销用户
*
* @return
*/
@PostMapping("/logout")
public ResultUtil logout(@RequestParam("accessToken") String accessToken, @RequestParam("refreshToken") String refreshToken) {
// 清除redis中的token
if (redisService.hasKey(TokenConstant.ACCESS_TOKEN + ":" + accessToken)) {
redisService.del(TokenConstant.ACCESS_TOKEN + ":" + accessToken);
}
if (redisService.hasKey(TokenConstant.REFRESH_TOKEN + ":" + refreshToken)) {
redisService.del(TokenConstant.REFRESH_TOKEN + ":" + refreshToken);
}
return ResultUtil.SUCCESS_NO_DATA;
}
/**
* 微信小程序根据wx.login获取openid、unionid、session_key
*
* @param code
* @return
* @throws Exception
*/
@GetMapping("wechat/login/{code}")
public ResultUtil weChatAppLogin(@PathVariable("code") String code) throws Exception {
log.info("===============进入微信小程序登录认证============");
Map param = new HashMap<>(4);
param.put("appid", weChatAppProperties.getAppId());
param.put("secret", weChatAppProperties.getSecret());
param.put("js_code", code);
param.put("grant_type", "authorization_code");
String result = HttpUtil.get(WeChatConstant.WECHAT_APP_SESSION, param);
log.info("##result={}", result);
cn.hutool.json.JSONObject jsonObject = JSONUtil.parseObj(result);
Map map = JSONUtil.toBean(jsonObject, Map.class);
// 如果返回正确 返回的信息中不带errcode和errmsg信息 返回错误会带有errcode和errmsg信息
if (ObjectUtil.isEmpty(map.get("errcode"))) {
String openid = map.get("openid").toString();
// unionid 必须开通微信公众平台及公众号才会显示
String unionid = ObjectUtil.isNotEmpty(map.get("unionid")) ? map.get("unionid").toString() : "";
String sessionKey = map.get("session_key").toString();
// 将信息存放到数据库中
SysWechatUserEntity sysWechatUser = sysWechatUserService.getByOpenid(openid);
if (ObjectUtil.isEmpty(sysWechatUser)) {
sysWechatUser = new SysWechatUserEntity();
sysWechatUser.setOpenid(openid);
sysWechatUser.setUnionid(unionid);
sysWechatUserService.save(sysWechatUser);
}
Authentication authenticate = null;
try {
// 通过UsernamePasswordAuthenticationToken获取用户名和密码
WechatAppAuthenticationToken authenticationToken = new WechatAppAuthenticationToken(openid, sessionKey);
// AuthenticationManager委托机制对authenticationToken 进行用户认证 去调用UserDetailsServiceImpl.loadUserByUsername
authenticate = authenticationManager.authenticate(authenticationToken);
// // 通过UsernamePasswordAuthenticationToken获取用户名和密码
// UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
// openid, null);
// // AuthenticationManager委托机制对authenticationToken 进行用户认证 去调用UserDetailsServiceImpl.loadUserByUsername
// authenticate = authenticationManager.authenticate(authenticationToken);
} catch (Exception e) {
log.error("##登录认证失败:{}", e.getMessage());
throw new ServiceException(SysResultEnum.WECHAT_USER_UPDATE_LOGIN_FAIL);
} finally {
SecurityContextHolder.clearContext();
}
// 如果认证通过,拿到这个当前登录用户信息
WechatAppUser wechatAppUser = (WechatAppUser) authenticate.getPrincipal();
wechatAppUser.setSessionKey(sessionKey);
TokenVO tokenVO = new TokenVO();
String currentTime = System.currentTimeMillis() + "";
Map tokenMap = new HashMap<>(3);
tokenMap.put(TokenConstant.LOGIN_DEVICE_WECHAT, TokenConstant.LOGIN_DEVICE_WECHAT);
tokenMap.put(TokenConstant.JWT_USER_ID, sysWechatUser.getId().toString());
tokenMap.put(TokenConstant.LOGIN_TIME, currentTime);
String access_token = JwtUtil.createJwt(tokenMap, TokenConstant.EXPIRES_IN);
String refresh_token = JwtUtil.createJwt(tokenMap, TokenConstant.REFRESH_EXPIRES_IN);
// 缓存微信小程序用户信息,用于token验证 获取用户信息
redisService.set(TokenConstant.LOGIN_USER_REDIS_KEY + TokenConstant.LOGIN_DEVICE_WECHAT + "_" + sysWechatUser.getId() + "_" + currentTime, wechatAppUser, TokenConstant.EXPIRES_IN);
tokenVO.setAccess_token(access_token).setRefresh_token(refresh_token).setExpires_in(TokenConstant.EXPIRES_IN);
return ResultUtil.success(tokenVO);
} else {
throw new ServiceException(Integer.valueOf(map.get("errcode").toString()), map.get("errmsg").toString());
}
}
}