SpringBoot3+SpringSecurity整合
Security导包:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
模拟Redis存储登录信息:
public class CacheEntity implements Serializable { private Object value; /** * 保存的时间戳 */ private long gmtModify; /** * 过期时间 */ private int expire; public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } public long getGmtModify() { return gmtModify; } public void setGmtModify(long gmtModify) { this.gmtModify = gmtModify; } public int getExpire() { return expire; } public void setExpire(int expire) { this.expire = expire; } public CacheEntity(Object value, long gmtModify, int expire) { this.value = value; this.gmtModify = gmtModify; this.expire = expire; } }
@Slf4j public class LocalCache { /** * 默认的缓存容量 */ private static final int DEFAULT_CAPACITY = 512; /** * 最大容量 */ private static final int MAX_CAPACITY = 100000; /** * 刷新缓存的频率 */ private static final int MONITOR_DURATION = 2; // 启动监控线程 static { new Thread(new TimeoutTimerThread()).start(); } // 内部类方式实现单例 private static class LocalCacheInstance { private static final LocalCache INSTANCE = new LocalCache(); } public static LocalCache getInstance() { return LocalCacheInstance.INSTANCE; } private LocalCache() { } /** * 使用默认容量创建一个Map */ private static Map<String, CacheEntity> cache = new ConcurrentHashMap<>(DEFAULT_CAPACITY); /** * 将key-value保存到本地缓存并制定该缓存的过期时间 * * @param key * @param value * @param expireTime 过期时间,如果是-1 则表示永不过期 * @param <T> * @return */ public <T> boolean putValue(String key, T value, int expireTime) { return putCloneValue(key, value, expireTime); } /** * 将值通过序列化clone 处理后保存到缓存中,可以解决值引用的问题 * * @param key * @param value * @param expireTime * @param <T> * @return */ private <T> boolean putCloneValue(String key, T value, int expireTime) { try { if (cache.size() >= MAX_CAPACITY) { return false; } // 序列化赋值 CacheEntity entityClone = clone(new CacheEntity(value, System.nanoTime(), expireTime)); cache.put(key, entityClone); return true; } catch (Exception e) { log.error("添加缓存失败:{}", e.getMessage()); } return false; } /** * 序列化 克隆处理 * * @param object * @param <E> * @return */ private <E extends Serializable> E clone(E object) { E cloneObject = null; try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(object); oos.close(); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); cloneObject = (E) ois.readObject(); ois.close(); } catch (Exception e) { log.error("缓存序列化失败:{}", e.getMessage()); } return cloneObject; } /** * 从本地缓存中获取key对应的值,如果该值不存则则返回null * * @param key * @return */ public Object getValue(String key) { if (CollectionUtils.isEmpty(cache)) { return null; } CacheEntity cacheEntity = cache.get(key); if (ObjectUtils.isEmpty(cacheEntity)) { return null; } return cacheEntity.getValue(); } public void remove(String key) { if (CollectionUtils.isEmpty(cache)) { return; } CacheEntity cacheEntity = cache.get(key); if (ObjectUtils.isEmpty(cacheEntity)) { return; } cache.remove(key); } /** * 清空所有 */ public void clear() { cache.clear(); } /** * 过期处理线程 */ static class TimeoutTimerThread implements Runnable { @Override public void run() { while (true) { try { TimeUnit.SECONDS.sleep(MONITOR_DURATION); checkTime(); } catch (Exception e) { log.error("过期缓存清理失败:{}", e.getMessage()); } } } /** * 过期缓存的具体处理方法 * * @throws Exception */ private void checkTime() throws Exception { // 开始处理过期 for (String key : cache.keySet()) { CacheEntity tce = cache.get(key); long timoutTime = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - tce.getGmtModify()); // 过期时间 : timoutTime if (tce.getExpire() > timoutTime) { continue; } log.info(" 清除过期缓存 :" + key); //清除过期缓存和删除对应的缓存队列 cache.remove(key); } } } }
权限枚举:
// 权限值是将二进制与十进制相互转换来判断的 public enum PermissionEnum { GET_DEPARTMENT(1, "单位获取", "ROLE_GET_DEPARTMENT", 0x0000000000000001L), INSERT_DEPARTMENT(2, "单位增加", "ROLE_INSERT_DEPARTMENT", 0x0000000000000002L), UPDATE_DEPARTMENT(3, "单位修改", "ROLE_UPDATE_DEPARTMENT", 0x0000000000000004L), DELETE_DEPARTMENT(4, "单位删除", "ROLE_DELETE_DEPARTMENT", 0x0000000000000008L), ; private int id; private String permissions; private String permissionNames; private Long value; PermissionEnum(int id, String permissions, String permissionNames, Long value) { this.id = id; this.permissions = permissions; this.permissionNames = permissionNames; this.value = value; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getPermissions() { return permissions; } public void setPermissions(String permissions) { this.permissions = permissions; } public String getPermissionNames() { return permissionNames; } public void setPermissionNames(String permissionNames) { this.permissionNames = permissionNames; } public Long getValue() { return value; } public void setValue(Long value) { this.value = value; } public static List<GrantedAuthority> fromCode(Long code) { List<GrantedAuthority> list = new ArrayList<>(); PermissionEnum[] codes = PermissionEnum.values(); for (PermissionEnum state : codes) { if ((state.getValue() & code) > 0) { list.add(new SimpleGrantedAuthority(state.getPermissionNames())); } } return list; } public static List<PermissionEnum> getAuthList(Long code) { List<PermissionEnum> list = new ArrayList<>(); PermissionEnum[] codes = PermissionEnum.values(); for (PermissionEnum state : codes) { if ((state.getValue() & code) > 0) { list.add(state); } } return list; } // 获取权限值 public static Long getPermissionCode(Integer[] auths) { Long code = 0x0000000000000000L; PermissionEnum[] codes = PermissionEnum.values(); for (Integer auth : auths) { for (PermissionEnum permissionCode : codes) { if (auth.equals(permissionCode.getId())) { code += permissionCode.getValue(); break; } } } return code; } // 获取权限数组 public static String[] getAuths(Long code) { List<String> lists = new ArrayList<>(); PermissionEnum[] codes = PermissionEnum.values(); for (PermissionEnum state : codes) { if ((state.getValue() & code) > 0) { lists.add(state.getPermissions()); } } return lists.toArray(new String[lists.size()]); } // 获取权限值 public static Long getPermissionCode(String[] auths) { Long code = 0x0000000000000000L; PermissionEnum[] codes = PermissionEnum.values(); for (String auth : auths) { for (PermissionEnum permissionCode : codes) { if (auth.equals(permissionCode.getPermissions())) { code += permissionCode.getValue(); break; } } } return code; } }
User实体类:
@Data @Accessors(chain = true) public class Users implements Serializable { private Long userID; private String userName; private String userPassword; private String userPhone; private String userAddress; private Integer userAllowErrCount; private Integer userErrCount; private Date userLastErrTime; private Long userRoleID; private Roles roles; private Long userDepID; private Department department; private boolean userEnable; }
权限反序列化:
public class CustomAuthorityDeserializer extends JsonDeserializer { @Override public Object deserialize( JsonParser p, DeserializationContext deserializationContext ) throws IOException, JacksonException { ObjectMapper mapper = (ObjectMapper) p.getCodec(); JsonNode jsonNode = mapper.readTree(p); LinkedList<GrantedAuthority> grantedAuthorities = new LinkedList<>(); Iterator<JsonNode> elements = jsonNode.elements(); while (elements.hasNext()) { JsonNode next = elements.next(); JsonNode authority = next.get("authority"); //将得到的值放入链表 最终返回该链表 grantedAuthorities.add(new SimpleGrantedAuthority(authority.asText())); } return grantedAuthorities; } }
用户详情类:
@JsonIgnoreProperties({"enabled", "accountNonExpired", "accountNonLocked", "credentialsNonExpired", "username", "password"}) public class MyUserDetail extends Users implements UserDetails, Serializable { List<? extends GrantedAuthority> authorities; public MyUserDetail() { } public MyUserDetail(Users users, List<? extends GrantedAuthority> authList) { this.setUserID(users.getUserID()); this.setUserName(users.getUserName()); this.setUserPassword(users.getUserPassword()); this.setUserDepID(users.getUserDepID()); this.setUserRoleID(users.getUserRoleID()); this.authorities = authList; } @Override @JsonDeserialize(using = CustomAuthorityDeserializer.class) public Collection<? extends GrantedAuthority> getAuthorities() { return this.authorities; } @Override public String getPassword() { return this.getUserPassword(); } @Override public String getUsername() { return this.getUserName(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }
用户详情实现类:
@Component public class MyUserDetailServiceImpl implements UserDetailsService { // 操作数据库,根据用户名称查询用户信息 private final UserMapper userMapper; public MyUserDetailServiceImpl(UserMapper userMapper) { this.userMapper = userMapper; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { Users users = userMapper.getUserByName(username); Optional.ofNullable(users).orElseThrow(() -> { // 自定义的异常返回类和枚举 throw new CommonException(YIXGResultEnum.USER_NOT_EXIST.getCode(), YIXGResultEnum.USER_NOT_EXIST.getMessage()); }); if (ObjectUtils.isEmpty(users.getUserRoleID())) { throw new CommonException(YIXGResultEnum.USER_ROLE_NOT_EXIST.getCode(), YIXGResultEnum.USER_ROLE_NOT_EXIST.getMessage()); } List<GrantedAuthority> authorityList = PermissionEnum.fromCode(users.getRoles().getRolePermission()); return new MyUserDetail(users, authorityList); } }
拦截未登录请求:
/** * 用户发起未登录的请求会被AuthorizationFilter拦截,并抛出AccessDeniedException异常。异常被AuthenticationEntryPoint * 处理,默认会触发重定向到登录页。Spring Security开放了配置,允许我们自定义AuthenticationEntryPoint。 * 那么我们就通过自定义AuthenticationEntryPoint来取消重定向行为,将接口改为返回JSON信息。 */ public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence( HttpServletRequest request, HttpServletResponse response, AuthenticationException authException ) throws IOException, ServletException { CommonResult commonResult = new CommonResult(); ObjectMapper objectMapper = new ObjectMapper(); commonResult.setCode(YIXGResultEnum.LOGIN_INVALID.getCode()) .setMessage(YIXGResultEnum.LOGIN_INVALID.getMessage()); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(objectMapper.writeValueAsString(commonResult)); response.getWriter().flush(); response.getWriter().close(); } }
拦截没权限的请求:
public class MyAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle( HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException ) throws IOException, ServletException { ObjectMapper objectMapper = new ObjectMapper(); CommonResult commonResult = new CommonResult(); commonResult.setCode(YIXGResultEnum.NO_PERMISSION.getCode()) .setMessage(YIXGResultEnum.NO_PERMISSION.getMessage()); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(objectMapper.writeValueAsString(commonResult)); response.getWriter().flush(); response.getWriter().close(); } }
自定义拦截器,验证token信息:
public class MyAuthenticationTokenFilter extends OncePerRequestFilter { @Override protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain ) throws ServletException, IOException { // 从header中获取验证信息 String authHeader = request.getHeader(GlobalUtil.AUTHORIZATION); if (ObjectUtils.isEmpty(authHeader)) { filterChain.doFilter(request, response); return; } this.doParse(request, response, filterChain, authHeader); } private void doParse( HttpServletRequest request, HttpServletResponse response, FilterChain chain, String authHeader ) throws ServletException, IOException { ObjectMapper objectMapper = new ObjectMapper(); // 如果认证码 以规定值开头 if (authHeader.startsWith(GlobalUtil.GRANT_TYPE)) { // 提取token值 String token = authHeader.substring(GlobalUtil.GRANT_TYPE.length()); if (ObjectUtils.isEmpty(token)) { chain.doFilter(request, response); return; } // 通过token值从缓存中取用户信息 String userJson = (String) LocalCache.getInstance().getValue(token); // 转换JSON对象 //JSONObject userJsonObject = JSON.parseObject(userJson); // 判断是否空值 if (ObjectUtils.isEmpty(userJson)) { // throw new CommonException(YIXGResultEnum.INVALID_TOKEN.getCode(), // YIXGResultEnum.INVALID_TOKEN.getMsg()); chain.doFilter(request, response); return; } // 转换MyUserDetail对象 MyUserDetail user = objectMapper.readValue(userJson, MyUserDetail.class); //MyUserDetail user = JSON.toJavaObject(userJsonObject, MyUserDetail.class); // MyUserDetail user = JSONObject.toJavaObject(userJsonObject, MyUserDetail.class); // 转换 UP 对象放到上下文中 UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken( user, user.getPassword(), user.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); } chain.doFilter(request, response); } }
密码加密:
public class MyPasswordEncoder implements PasswordEncoder { @Override public String encode(CharSequence rawPassword) { return MD5Util.md5((String) rawPassword); } @Override public boolean matches(CharSequence rawPassword, String encodedPassword) { return encodedPassword.equalsIgnoreCase(MD5Util.md5((String) rawPassword)); } }
登录成功:
@Component public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler { private final ObjectMapper objectMapper; private final LogService logService; private final UserService userService; public MyAuthenticationSuccessHandler( ObjectMapper objectMapper, LogService logService, UserService userService ) { this.objectMapper = objectMapper; this.logService = logService; this.userService = userService; } @Override public void onAuthenticationSuccess( HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication ) throws IOException, ServletException { AuthenticationSuccessHandler.super.onAuthenticationSuccess(request, response, chain, authentication); } @Override public void onAuthenticationSuccess( HttpServletRequest request, HttpServletResponse response, Authentication authentication ) throws IOException, ServletException { MyUserDetail user = (MyUserDetail) authentication.getPrincipal(); // 获取随机token 并存到Redis中 String token = UUID.randomUUID().toString().replaceAll("-", ""); LocalCache.getInstance().putValue(token, objectMapper.writeValueAsString(user), 60 * 60); UserVO userVO = new UserVO(); userVO.setUserName(user.getUserName()) .setUserErrCount("0") .setUserLastErrTime(null); userService.updateUserErrCount(userVO); LogVO logVO = new LogVO(); logVO.setLogOperateUser(user.getUserName()) .setLogContent("登录成功") .setLogType("登录日志"); logService.addLog(logVO); CommonResult commonResult = new CommonResult(); commonResult.setCode(YIXGResultEnum.OPERATE_SUCCESS.getCode()) .setMessage(YIXGResultEnum.OPERATE_SUCCESS.getMessage()) .setToken(token) .setCurrentUser(user.getUserName()) .setCurrentUserId(user.getUserID()); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(objectMapper.writeValueAsString(commonResult)); response.getWriter().flush(); response.getWriter().close(); } }
登录失败:
@Component @Slf4j public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler { private final ObjectMapper objectMapper; private final UserService userService; public MyAuthenticationFailureHandler(ObjectMapper objectMapper, UserService userService) { this.objectMapper = objectMapper; this.userService = userService; } @Override public void onAuthenticationFailure( HttpServletRequest request, HttpServletResponse response, AuthenticationException exception ) throws IOException, ServletException { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); CommonResult result = userService.getUserByUserName( new UserVO().setUserName(request.getParameter("username"))); Users users = (Users) result.getObjectData(); if (Objects.equals(result.getCode(), YIXGResultEnum.OPERATE_SUCCESS.getCode())) { UserVO userVO = new UserVO(); userVO.setUserName(users.getUserName()) .setUserErrCount(String.valueOf((users.getUserErrCount() + 1))) .setUserLastErrTime(sdf.format(new Date())); userService.updateUserErrCount(userVO); } CommonResult commonResult = new CommonResult(); commonResult.setCode(YIXGResultEnum.PASSWORD_OR_USERNAME_ERROR.getCode()) .setMessage(YIXGResultEnum.PASSWORD_OR_USERNAME_ERROR.getMessage()); response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); response.getWriter().write(objectMapper.writeValueAsString(commonResult)); response.getWriter().flush(); response.getWriter().close(); } }
登出成功:
@Component public class MyLogoutSuccessHandler implements LogoutSuccessHandler { private final ObjectMapper objectMapper; private final LogService logService; public MyLogoutSuccessHandler(ObjectMapper objectMapper, LogService logService) { this.objectMapper = objectMapper; this.logService = logService; } @Override public void onLogoutSuccess( HttpServletRequest request, HttpServletResponse response, Authentication authentication ) throws IOException, ServletException { String authHeader = request.getHeader(GlobalUtil.AUTHORIZATION); String authToken = authHeader.substring(GlobalUtil.GRANT_TYPE.length()); String userJson = (String) LocalCache.getInstance().getValue(authToken); if (ObjectUtils.isEmpty(userJson)) { CommonResult commonResult = new CommonResult(); commonResult.setCode(YIXGResultEnum.OPERATE_FAILURE.getCode()) .setMessage(YIXGResultEnum.OPERATE_FAILURE.getMessage()); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(objectMapper.writeValueAsString(commonResult)); response.getWriter().flush(); response.getWriter().close(); return; } MyUserDetail user = objectMapper.readValue(userJson, MyUserDetail.class); LocalCache.getInstance().putValue(authToken, "", 1); LogVO logVO = new LogVO(); logVO.setLogOperateUser(user.getUserName()) .setLogContent("登出成功") .setLogType("登录日志"); logService.addLog(logVO); CommonResult commonResult = new CommonResult(); commonResult.setCode(YIXGResultEnum.OPERATE_SUCCESS.getCode()) .setMessage(YIXGResultEnum.OPERATE_SUCCESS.getMessage()); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(objectMapper.writeValueAsString(commonResult)); response.getWriter().flush(); response.getWriter().close(); } }
Security核心配置:
@Configuration @EnableWebSecurity public class SecurityConfig { private final MyAuthenticationSuccessHandler myAuthenticationSuccessHandler; private final MyAuthenticationFailureHandler myAuthenticationFailureHandler; private final MyLogoutSuccessHandler myLogoutSuccessHandler; private final UserDetailsService userDetailsService; public SecurityConfig( MyAuthenticationSuccessHandler myAuthenticationSuccessHandler, MyAuthenticationFailureHandler myAuthenticationFailureHandler, MyLogoutSuccessHandler myLogoutSuccessHandler, UserDetailsService userDetailsService ) { this.myAuthenticationSuccessHandler = myAuthenticationSuccessHandler; this.myAuthenticationFailureHandler = myAuthenticationFailureHandler; this.myLogoutSuccessHandler = myLogoutSuccessHandler; this.userDetailsService = userDetailsService; } @Bean public AuthenticationManager authenticationManager( AuthenticationConfiguration authenticationConfiguration ) throws Exception { return authenticationConfiguration.getAuthenticationManager(); } @Bean public PasswordEncoder passwordEncoder() { return new MyPasswordEncoder(); } @Bean public MyAuthenticationTokenFilter myAuthenticationTokenFilter() { return new MyAuthenticationTokenFilter(); } @Bean SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { return httpSecurity // 禁用basic明文验证 .httpBasic(Customizer.withDefaults()) // 基于 token ,不需要 csrf .csrf(AbstractHttpConfigurer::disable) // 禁用默认登录页 .formLogin(fl -> fl.loginProcessingUrl("/login") .usernameParameter("username") .passwordParameter("password") .successHandler(myAuthenticationSuccessHandler) .failureHandler(myAuthenticationFailureHandler) .permitAll()) // 禁用默认登出页 .logout(lt -> lt.logoutSuccessHandler(myLogoutSuccessHandler)) // 基于 token , 不需要 session .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 设置 处理鉴权失败、认证失败 .exceptionHandling( exceptions -> exceptions.authenticationEntryPoint(new MyAuthenticationEntryPoint()) .accessDeniedHandler(new MyAccessDeniedHandler()) ) // 下面开始设置权限 .authorizeHttpRequests(authorizeHttpRequest -> authorizeHttpRequest // 允许所有 OPTIONS 请求 .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() // 允许直接访问 授权登录接口 .requestMatchers(HttpMethod.POST, "/web/authenticate").permitAll() // 允许 SpringMVC 的默认错误地址匿名访问 .requestMatchers("/error").permitAll() // 其他所有接口必须有Authority信息,Authority在登录成功后的UserDetailImpl对象中默认设置“ROLE_USER” //.requestMatchers("/**").hasAnyAuthority("ROLE_USER") .requestMatchers("/heartBeat/**", "/main/**").permitAll() // 允许任意请求被已登录用户访问,不检查Authority .anyRequest().authenticated() ) // 添加过滤器 .addFilterBefore(myAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class) .build(); } @Bean public UserDetailsService userDetailsService() { return userDetailsService::loadUserByUsername; } /** * 调用loadUserByUserName获取userDetail信息,在AbstractUserDetailsAuthenticationProvider里执行用户状态检查 * * @return */ @Bean public AuthenticationProvider authenticationProvider() { DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); authProvider.setUserDetailsService(userDetailsService); authProvider.setPasswordEncoder(passwordEncoder()); return authProvider; } // @Bean // public WebSecurityCustomizer webSecurityCustomizer() { // return (web) -> web.ignoring().requestMatchers(); // } /** * 配置跨源访问(CORS) * * @return */ @Bean CorsConfigurationSource corsConfigurationSource() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues()); return source; } }
转: https://blog.csdn.net/qq_40107343/article/details/136086463
标签:return,String,private,认证,springboot3,response,new,security,public From: https://www.cnblogs.com/fps2tao/p/18225317