目录
Spring-Security
Spring-Webflux
注意
编码
项目环境版本
gradle 依赖
Spring-Security配置
1. Security核心配置
2.用户认证
3.1 自定义登录成功Handler
3.2 自定义登录失败Handler
3.3 自定义未认证Handler
3.4 自定义鉴权失败Handler
4.自定义JWT Token认证管理
5.自定义鉴权管理
2.springsecruity密码判断
3.流程
3.尚硅谷springsecurity
3.3两个重要接口
3.3.1认证
3.3.2自定义登入
403设计编辑
3.5用户注销
3.6免登陆
4过滤器方式
4.1maven—直接引用–配置
4.2 entity
4.3 filter
4.3.1访问过滤器 获取request–token
4.3.2登录过滤器 成功 获取token 保存redis
4.4密码的处理方法类型
4.4退出
4.5token生成
4.6未授权
4.7 XML
4.7 jwt
4.8 MD5
4.9 R
Spring Cloud Gateway是基于Spring Boot 2.x,Spring WebFlux和Project Reactor构建的。结果,当您使用Spring Cloud Gateway时,许多您熟悉的同步库(例如,Spring Data和Spring Security)和模式可能不适用。如果您不熟悉这些项目,建议您在使用Spring Cloud Gateway之前先阅读它们的文档以熟悉一些新概念。
Spring-Security
Spring Security是一个提供身份验证,授权和保护以防止常见攻击的框架。凭借对命令式和响应式应用程序的一流支持,它是用于保护基于Spring的应用程序的事实上的标准。
Spring-Webflux
Spring框架中包含的原始Web框架Spring Web MVC是专门为Servlet API和Servlet容器而构建的。响应式堆栈Web框架Spring WebFlux在稍后的5.0版中添加。它是完全无阻塞的,支持 Reactive Streams背压,并在Netty,Undertow和Servlet 3.1+容器等服务器上运行。
这两个Web框架都反映了其源模块的名称(spring-webmvc和 spring-webflux),并在Spring Framework中并存。每个模块都是可选的。应用程序可以使用一个模块,也可以使用两个模块,在某些情况下,也可以使用两个模块,例如,带有react的Spring MVC控制器WebClient
。
注意
由于Web容器不同,在Gateway项目中使用的WebFlux,是不能和Spring-Web混合使用的。 Spring MVC和 WebFlux 的区别:
编码
项目环境版本
- Spring-Cloud:2020.0.1
- Spring-Boot: 2.4.3
gradle 依赖
dependencies { implementation('org.springframework.cloud:spring-cloud-starter-gateway','org.springframework.boot:spring-boot-starter-security' )}复制代码
Spring-Security配置
spring security设置要采用响应式配置,基于WebFlux中WebFilter实现,与Spring MVC的Security是通过Servlet的Filter实现类似,也是一系列filter组成的过滤链。
Reactor与传统MVC配置对应:
webflux | mvc | 作用 |
---|---|---|
@EnableWebFluxSecurity | @EnableWebSecurity | 开启security配置 |
ServerAuthenticationSuccessHandler | AuthenticationSuccessHandler | 登录成功Handler |
ServerAuthenticationFailureHandler | AuthenticationFailureHandler | 登陆失败Handler |
ReactiveAuthorizationManager | AuthorizationManager | 认证管理 |
ServerSecurityContextRepository | SecurityContextHolder | 认证信息存储管理 |
ReactiveUserDetailsService | UserDetailsService | 用户登录 |
ReactiveAuthorizationManager | AccessDecisionManager | 鉴权管理 |
ServerAuthenticationEntryPoint | AuthenticationEntryPoint | 未认证Handler |
ServerAccessDeniedHandler | AccessDeniedHandler | 鉴权失败Handler |
1. Security核心配置
package com.pluto.gateway.security;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.http.HttpMethod;import org.springframework.security.authentication.DelegatingReactiveAuthenticationManager;import org.springframework.security.authentication.ReactiveAuthenticationManager;import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager;import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;import org.springframework.security.config.web.server.ServerHttpSecurity;import org.springframework.security.crypto.factory.PasswordEncoderFactories;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.web.server.SecurityWebFilterChain;import reactor.core.publisher.Mono;import javax.annotation.Resource;import java.util.LinkedList;/** * @author ShiLei * @version 1.0.0 * @date 2021/3/11 10:56 * @description webflux security核心配置类 */@EnableWebFluxSecuritypublic class WebfluxSecurityConfig {@Resourceprivate DefaultAuthorizationManager defaultAuthorizationManager;@Resourceprivate UserDetailsServiceImpl userDetailsServiceImpl;@Resourceprivate DefaultAuthenticationSuccessHandler defaultAuthenticationSuccessHandler;@Resourceprivate DefaultAuthenticationFailureHandler defaultAuthenticationFailureHandler;@Resourceprivate TokenAuthenticationManager tokenAuthenticationManager;@Resourceprivate DefaultSecurityContextRepository defaultSecurityContextRepository;@Resourceprivate DefaultAuthenticationEntryPoint defaultAuthenticationEntryPoint;@Resourceprivate DefaultAccessDeniedHandler defaultAccessDeniedHandler;/*** 自定义过滤权限*/@Value("${security.noFilter}")private String noFilter;@Bean public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity httpSecurity) {httpSecurity// 登录认证处理.authenticationManager(reactiveAuthenticationManager()).securityContextRepository(defaultSecurityContextRepository)// 请求拦截处理.authorizeExchange(exchange -> exchange.pathMatchers(noFilter).permitAll().pathMatchers(HttpMethod.OPTIONS).permitAll().anyExchange().access(defaultAuthorizationManager)).formLogin()// 自定义处理.authenticationSuccessHandler(defaultAuthenticationSuccessHandler).authenticationFailureHandler(defaultAuthenticationFailureHandler).and().exceptionHandling().authenticationEntryPoint(defaultAuthenticationEntryPoint).and().exceptionHandling().accessDeniedHandler(defaultAccessDeniedHandler).and().csrf().disable();return httpSecurity.build(); }/** * BCrypt密码编码 */ @Bean("passwordEncoder") public PasswordEncoder passwordEncoder() {return PasswordEncoderFactories.createDelegatingPasswordEncoder(); }/** * 注册用户信息验证管理器,可按需求添加多个按顺序执行 */ @Bean ReactiveAuthenticationManager reactiveAuthenticationManager() {LinkedList managers = new LinkedList(); managers.add(authentication -> {// 其他登陆方式 (比如手机号验证码登陆) 可在此设置不得抛出异常或者 Mono.error return Mono.empty(); }); // 必须放最后不然会优先使用用户名密码校验但是用户名密码不对时此 AuthenticationManager 会调用 Mono.error 造成后面的 AuthenticationManager 不生效 managers.add(new UserDetailsRepositoryReactiveAuthenticationManager(userDetailsServiceImpl)); managers.add(tokenAuthenticationManager); return new DelegatingReactiveAuthenticationManager(managers); }}复制代码
2.用户认证
package com.pluto.gateway.security;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.userdetails.User;import java.io.Serializable;import java.util.Collection;/** * @author ShiLei * @version 1.0.0 * @date 2021/3/10 13:15 * @description 自定义用户信息 */public class SecurityUserDetails extends User implements Serializable {private Long userId;public SecurityUserDetails(String username, String password, Collection authorities, Long userId) {super(username, password, authorities);this.userId = userId;}public SecurityUserDetails(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection authorities, Long userId) {super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);this.userId = userId;}public Long getUserId() {return userId;}public void setUserId(Long userId) {this.userId = userId;}}复制代码
package com.pluto.gateway.security;import org.springframework.security.core.userdetails.ReactiveUserDetailsPasswordService;import org.springframework.security.core.userdetails.ReactiveUserDetailsService;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.stereotype.Service;import reactor.core.publisher.Mono;import javax.annotation.Resource;import java.util.ArrayList;/** * @author ceshi * @date 2021/3/9 14:03 * @description 用户登录处理 * @version 1.0.0 */@Servicepublic class UserDetailsServiceImpl implements ReactiveUserDetailsService {@Resourceprivate PasswordEncoder passwordEncoder;@Overridepublic Mono findByUsername(String username) {SecurityUserDetails securityUserDetails = new SecurityUserDetails("user",passwordEncoder.encode("user"),true, true, true, true, new ArrayList(),1L);return Mono.just(securityUserDetails);}}复制代码
3.1 自定义登录成功Handler
package com.pluto.gateway.security;import com.alibaba.fastjson.JSONObject;import com.pluto.common.basic.utils.JwtTokenUtil;import com.pluto.common.basic.utils.ResultVoUtil;import org.springframework.beans.factory.annotation.Value;import org.springframework.core.io.buffer.DataBuffer;import org.springframework.core.io.buffer.DataBufferFactory;import org.springframework.security.core.Authentication;import org.springframework.security.web.server.WebFilterExchange;import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;import org.springframework.stereotype.Component;import reactor.core.publisher.Mono;import java.util.HashMap;import java.util.Map;/** * @author ShiLei * @version 1.0.0 * @date 2021/3/11 15:00 * @description 登录成功处理 */@Componentpublic class DefaultAuthenticationSuccessHandler implements ServerAuthenticationSuccessHandler {/*** token 过期时间*/@Value("${jwt.token.expired}")private int jwtTokenExpired;/*** 刷新token 时间*/@Value("${jwt.token.refresh.expired}")private int jwtTokenRefreshExpired;@Overridepublic Mono onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {return Mono.defer(() -> Mono.just(webFilterExchange.getExchange().getResponse()).flatMap(response -> {DataBufferFactory dataBufferFactory = response.bufferFactory();// 生成JWT tokenMap map = new HashMap(2);SecurityUserDetails userDetails = (SecurityUserDetails) authentication.getPrincipal();map.put("userId", userDetails.getUserId());map.put("username", userDetails.getUsername());map.put("roles",userDetails.getAuthorities());String token = JwtTokenUtil.generateToken(map, userDetails.getUsername(), jwtTokenExpired);String refreshToken = JwtTokenUtil.generateToken(map, userDetails.getUsername(), jwtTokenRefreshExpired);Map tokenMap = new HashMap(2);tokenMap.put("token", token);tokenMap.put("refreshToken", refreshToken);DataBuffer dataBuffer = dataBufferFactory.wrap(JSONObject.toJSONString(ResultVoUtil.success(tokenMap)).getBytes());return response.writeWith(Mono.just(dataBuffer));})); }}复制代码
3.2 自定义登录失败Handler
package com.pluto.gateway.security;import com.alibaba.fastjson.JSONObject;import com.pluto.common.basic.enums.UserStatusCodeEnum;import com.pluto.common.basic.utils.ResultVoUtil;import com.pluto.common.basic.vo.ResultVO;import org.springframework.core.io.buffer.DataBuffer;import org.springframework.core.io.buffer.DataBufferFactory;import org.springframework.security.authentication.*;import org.springframework.security.core.AuthenticationException;import org.springframework.security.core.userdetails.UsernameNotFoundException;import org.springframework.security.web.server.WebFilterExchange;import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;import org.springframework.stereotype.Component;import reactor.core.publisher.Mono;import java.util.Map;/** * @author ShiLei * @version 1.0.0 * @date 2021/3/11 15:14 * @description 登录失败处理 */@Componentpublic class DefaultAuthenticationFailureHandler implements ServerAuthenticationFailureHandler {@Overridepublic Mono onAuthenticationFailure(WebFilterExchange webFilterExchange, AuthenticationException exception) {return Mono.defer(() -> Mono.just(webFilterExchange.getExchange().getResponse()).flatMap(response -> {DataBufferFactory dataBufferFactory = response.bufferFactory();ResultVO<Map> resultVO = ResultVoUtil.error();// 账号不存在if (exception instanceof UsernameNotFoundException) {resultVO = ResultVoUtil.failed(UserStatusCodeEnum.ACCOUNT_NOT_EXIST);// 用户名或密码错误} else if (exception instanceof BadCredentialsException) {resultVO = ResultVoUtil.failed(UserStatusCodeEnum.LOGIN_PASSWORD_ERROR);// 账号已过期} else if (exception instanceof AccountExpiredException) {resultVO = ResultVoUtil.failed(UserStatusCodeEnum.ACCOUNT_EXPIRED);// 账号已被锁定} else if (exception instanceof LockedException) {resultVO = ResultVoUtil.failed(UserStatusCodeEnum.ACCOUNT_LOCKED);// 用户凭证已失效} else if (exception instanceof CredentialsExpiredException) {resultVO = ResultVoUtil.failed(UserStatusCodeEnum.ACCOUNT_CREDENTIAL_EXPIRED);// 账号已被禁用} else if (exception instanceof DisabledException) {resultVO = ResultVoUtil.failed(UserStatusCodeEnum.ACCOUNT_DISABLE);}DataBuffer dataBuffer = dataBufferFactory.wrap(JSONObject.toJSONString(resultVO).getBytes());return response.writeWith(Mono.just(dataBuffer));}));}}复制代码
3.3 自定义未认证Handler
package com.pluto.gateway.security;import com.alibaba.fastjson.JSONObject;import com.pluto.common.basic.enums.UserStatusCodeEnum;import com.pluto.common.basic.utils.ResultVoUtil;import org.springframework.core.io.buffer.DataBuffer;import org.springframework.core.io.buffer.DataBufferFactory;import org.springframework.http.HttpStatus;import org.springframework.http.MediaType;import org.springframework.security.core.AuthenticationException;import org.springframework.security.web.server.ServerAuthenticationEntryPoint;import org.springframework.stereotype.Component;import org.springframework.web.server.ServerWebExchange;import reactor.core.publisher.Mono;import java.nio.charset.Charset;/** * @author ShiLei * @version 1.0.0 * @date 2021/3/11 15:17 * @description 未认证处理 */@Componentpublic class DefaultAuthenticationEntryPoint implements ServerAuthenticationEntryPoint {@Overridepublic Mono commence(ServerWebExchange exchange, AuthenticationException ex) {return Mono.defer(() -> Mono.just(exchange.getResponse())).flatMap(response -> {response.setStatusCode(HttpStatus.UNAUTHORIZED);response.getHeaders().setContentType(MediaType.APPLICATION_JSON);DataBufferFactory dataBufferFactory = response.bufferFactory();String result = JSONObject.toJSONString(ResultVoUtil.failed(UserStatusCodeEnum.USER_UNAUTHORIZED));DataBuffer buffer = dataBufferFactory.wrap(result.getBytes(Charset.defaultCharset()));return response.writeWith(Mono.just(buffer));});}}复制代码
3.4 自定义鉴权失败Handler
package com.pluto.gateway.security;import com.alibaba.fastjson.JSONObject;import com.pluto.common.basic.enums.UserStatusCodeEnum;import com.pluto.common.basic.utils.ResultVoUtil;import org.springframework.core.io.buffer.DataBuffer;import org.springframework.core.io.buffer.DataBufferFactory;import org.springframework.http.HttpStatus;import org.springframework.http.MediaType;import org.springframework.security.access.AccessDeniedException;import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;import org.springframework.stereotype.Component;import org.springframework.web.server.ServerWebExchange;import reactor.core.publisher.Mono;import java.nio.charset.Charset;/** * @author ShiLei * @version 1.0.0 * @date 2021/3/11 11:12 * @description 鉴权管理 */@Componentpublic class DefaultAccessDeniedHandler implements ServerAccessDeniedHandler {@Overridepublic Mono handle(ServerWebExchange exchange, AccessDeniedException denied) {return Mono.defer(() -> Mono.just(exchange.getResponse())).flatMap(response -> {response.setStatusCode(HttpStatus.OK);response.getHeaders().setContentType(MediaType.APPLICATION_JSON);DataBufferFactory dataBufferFactory = response.bufferFactory();String result = JSONObject.toJSONString(ResultVoUtil.failed(UserStatusCodeEnum.PERMISSION_DENIED));DataBuffer buffer = dataBufferFactory.wrap(result.getBytes(Charset.defaultCharset()));return response.writeWith(Mono.just(buffer));});}}复制代码
4.自定义JWT Token认证管理
package com.pluto.gateway.security;import org.apache.commons.lang3.StringUtils;import org.springframework.http.server.reactive.ServerHttpRequest;import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;import org.springframework.security.core.context.SecurityContext;import org.springframework.security.core.context.SecurityContextImpl;import org.springframework.security.web.server.context.ServerSecurityContextRepository;import org.springframework.stereotype.Component;import org.springframework.util.CollectionUtils;import org.springframework.web.server.ServerWebExchange;import reactor.core.publisher.Mono;import javax.annotation.Resource;import java.util.List;/** * @author ShiLei * @version 1.0.0 * @date 2021/3/11 16:27 * @description 存储认证授权的相关信息 */@Componentpublic class DefaultSecurityContextRepository implements ServerSecurityContextRepository {public final static String TOKEN_HEADER = "Authorization";public final static String BEARER = "Bearer ";@Resourceprivate TokenAuthenticationManager tokenAuthenticationManager;@Overridepublic Mono save(ServerWebExchange exchange, SecurityContext context) {return Mono.empty();}@Overridepublic Mono load(ServerWebExchange exchange) {ServerHttpRequest request = exchange.getRequest();List headers = request.getHeaders().get(TOKEN_HEADER);if (!CollectionUtils.isEmpty(headers)) {String authorization = headers.get(0);if (StringUtils.isNotEmpty(authorization)) {String token = authorization.substring(BEARER.length());if (StringUtils.isNotEmpty(token)) {return tokenAuthenticationManager.authenticate(new UsernamePasswordAuthenticationToken(token, null)).map(SecurityContextImpl::new);}}}return Mono.empty();}}复制代码
package com.pluto.gateway.security;import com.pluto.common.basic.utils.JwtTokenUtil;import org.springframework.context.annotation.Primary;import org.springframework.security.authentication.ReactiveAuthenticationManager;import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;import org.springframework.security.core.Authentication;import org.springframework.security.core.GrantedAuthority;import org.springframework.stereotype.Component;import reactor.core.publisher.Mono;import java.util.Collection;/** * @author ShiLei * @version 1.0.0 * @date 2021/3/11 13:23 * @description token 认证处理 */@Component@Primarypublic class TokenAuthenticationManager implements ReactiveAuthenticationManager {@Override@SuppressWarnings("unchecked")public Mono authenticate(Authentication authentication) {return Mono.just(authentication).map(auth -> JwtTokenUtil.parseJwtRsa256(auth.getPrincipal().toString())).map(claims -> {Collection roles = (Collection) claims.get("roles");return new UsernamePasswordAuthenticationToken(claims.getSubject(),null,roles);});}}复制代码
5.自定义鉴权管理
package com.pluto.gateway.security;import com.alibaba.fastjson.JSONObject;import com.pluto.common.basic.enums.UserStatusCodeEnum;import com.pluto.common.basic.utils.ResultVoUtil;import lombok.extern.slf4j.Slf4j;import org.springframework.http.server.reactive.ServerHttpRequest;import org.springframework.security.access.AccessDeniedException;import org.springframework.security.authorization.AuthorizationDecision;import org.springframework.security.authorization.ReactiveAuthorizationManager;import org.springframework.security.core.Authentication;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.web.server.authorization.AuthorizationContext;import org.springframework.stereotype.Component;import org.springframework.util.AntPathMatcher;import org.springframework.web.server.ServerWebExchange;import reactor.core.publisher.Mono;import java.util.Collection;/** * @author ShiLei * @version 1.0.0 * @date 2021/3/11 13:10 * @description 用户权限鉴权处理 */@Component@Slf4jpublic class DefaultAuthorizationManager implements ReactiveAuthorizationManager {private final AntPathMatcher antPathMatcher = new AntPathMatcher();@Overridepublic Mono check(Mono authentication, AuthorizationContext authorizationContext) {return authentication.map(auth -> {ServerWebExchange exchange = authorizationContext.getExchange();ServerHttpRequest request = exchange.getRequest();Collection authorities = auth.getAuthorities();for (GrantedAuthority authority : authorities) {String authorityAuthority = authority.getAuthority();String path = request.getURI().getPath();// TODO// 查询用户访问所需角色进行对比if (antPathMatcher.match(authorityAuthority, path)) {log.info(String.format("用户请求API校验通过,GrantedAuthority:{%s}Path:{%s} ", authorityAuthority, path));return new AuthorizationDecision(true);}}return new AuthorizationDecision(false);}).defaultIfEmpty(new AuthorizationDecision(false));}@Overridepublic Mono verify(Mono authentication, AuthorizationContext object) {return check(authentication, object).filter(AuthorizationDecision::isGranted).switchIfEmpty(Mono.defer(() -> {String body = JSONObject.toJSONString(ResultVoUtil.failed(UserStatusCodeEnum.PERMISSION_DENIED));return Mono.error(new AccessDeniedException(body));})).flatMap(d -> Mono.empty());}}复制代码
2.springsecruity密码判断
下面看看是哪里进行的密码比较
1 /spring-security-core-5.1.4.RELEASE-sources.jar!/org/springframework/security/authentication/dao/AbstractUserDetailsAuthenticationProvider.java
public Authentication authenticate(Authentication authentication)throws AuthenticationException { ...... try { preAuthenticationChecks.check(user); // 重点看additionalAuthenticationChecks密码判断 additionalAuthenticationChecks(user,(UsernamePasswordAuthenticationToken) authentication); } catch (AuthenticationException exception) { if (cacheWasUsed) {// There was a problem, so try again after checking// we're using latest data (i.e. not from the cache)cacheWasUsed = false;user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);preAuthenticationChecks.check(user);additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication); } else {throw exception; } } postAuthenticationChecks.check(user); ...... return createSuccessAuthentication(principalToReturn, authentication, user);}
2 /spring-security-core-5.1.4.RELEASE-sources.jar!/org/springframework/security/authentication/dao/DaoAuthenticationProvider.javaprotected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { if (authentication.getCredentials() == null) { logger.debug("Authentication failed: no credentials provided"); throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials","Bad credentials")); } String presentedPassword = authentication.getCredentials().toString(); // 密码比较就在这个地方,前面这个是用户输入的密码,后面这个是数据库存的密码,一致则通过 if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) { logger.debug("Authentication failed: password does not match stored value"); throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials","Bad credentials")); }}
3.流程
用户信息 还可以存放权限信息
3.尚硅谷springsecurity
3.3两个重要接口
3.3.1认证
3.3.2自定义登入
3.3.3
403设计
3.5注解访问
3.5用户注销
3.6免登陆
密码
4过滤器方式
4.1maven—直接引用–配置
package com.atguigu.serurity.config;import com.atguigu.serurity.filter.TokenAuthenticationFilter;import com.atguigu.serurity.filter.TokenLoginFilter;import com.atguigu.serurity.security.DefaultPasswordEncoder;import com.atguigu.serurity.security.TokenLogoutHandler;import com.atguigu.serurity.security.TokenManager;import com.atguigu.serurity.security.UnauthorizedEntryPoint;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;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.builders.WebSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.core.userdetails.UserDetailsService;/** * * Security配置类 *
* * @author qy * @since 2019-11-18 */@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true)public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {private UserDetailsService userDetailsService;//用户详情服务private TokenManager tokenManager;//令牌管理器private DefaultPasswordEncoder defaultPasswordEncoder;//默认密码编码器private RedisTemplate redisTemplate;//Redis 模板//令牌网络安全配置@Autowiredpublic TokenWebSecurityConfig(UserDetailsService userDetailsService, DefaultPasswordEncoder defaultPasswordEncoder,TokenManager tokenManager, RedisTemplate redisTemplate) {this.userDetailsService = userDetailsService;this.defaultPasswordEncoder = defaultPasswordEncoder;this.tokenManager = tokenManager;this.redisTemplate = redisTemplate;}/** * 配置设置 * @param http * @throws Exception */@Overrideprotected void configure(HttpSecurity http) throws Exception {System.out.println("configure(HttpSecurity http)"+http);http.exceptionHandling().authenticationEntryPoint(new UnauthorizedEntryPoint()).and().csrf().disable().authorizeRequests().anyRequest().authenticated().and().logout().logoutUrl("/admin/acl/index/logout").addLogoutHandler(new TokenLogoutHandler(tokenManager,redisTemplate)).and().addFilter(new TokenLoginFilter(authenticationManager(), tokenManager, redisTemplate)).addFilter(new TokenAuthenticationFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic();}/** * 密码处理 * @param auth * @throws Exception * 身份验证管理器生成器 */@Overridepublic void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder);}/** * 配置哪些请求不拦截 * @param web * @throws Exception */@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/api/**","/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**" );}}
4.2 entity
package com.atguigu.serurity.entity;import lombok.Data;import lombok.extern.slf4j.Slf4j;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.authority.SimpleGrantedAuthority;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.util.StringUtils;import java.util.ArrayList;import java.util.Collection;import java.util.List;/** * * 安全认证用户详情信息 *
* * @author qy * @since 2019-11-08 */@Data@Slf4jpublic class SecurityUser implements UserDetails {//当前登录用户private transient User currentUserInfo;//当前权限private List permissionValueList;public SecurityUser() {}public SecurityUser(User user) {if (user != null) {this.currentUserInfo = user;}}@Overridepublic Collectionpackage com.atguigu.serurity.entity;import io.swagger.annotations.ApiModel;import io.swagger.annotations.ApiModelProperty;import lombok.Builder;import lombok.Data;import java.io.Serializable;/** * * 用户实体类 *
* * @author qy * @since 2019-11-08 */@Data@ApiModel(description = "用户实体类")public class User implements Serializable {private static final long serialVersionUID = 1L;@ApiModelProperty(value = "微信openid")private String username;@ApiModelProperty(value = "密码")private String password;@ApiModelProperty(value = "昵称")private String nickName;@ApiModelProperty(value = "用户头像")private String salt;@ApiModelProperty(value = "用户签名")private String token;}
4.3 filter
4.3.1访问过滤器 获取request–token
package com.atguigu.serurity.filter;import com.atguigu.commonutils.R;import com.atguigu.commonutils.ResponseUtil;import com.atguigu.serurity.security.TokenManager;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.authority.SimpleGrantedAuthority;import org.springframework.security.core.context.SecurityContextHolder;import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;import org.springframework.util.StringUtils;import javax.servlet.FilterChain;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.util.ArrayList;import java.util.Collection;import java.util.List;/** * * 访问过滤器 *
* * @author qy * @since 2019-11-08 */public class TokenAuthenticationFilter extends BasicAuthenticationFilter {private TokenManager tokenManager;private RedisTemplate redisTemplate;public TokenAuthenticationFilter(AuthenticationManager authManager, TokenManager tokenManager,RedisTemplate redisTemplate) {super(authManager);this.tokenManager = tokenManager;this.redisTemplate = redisTemplate;}@Overrideprotected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)throws IOException, ServletException {logger.info("=======TokenAuthenticationFilter-doFilterInternal=========="+req.getRequestURI());System.out.println("req.getRequestURI().indexOf(\"admin\")"+req.getRequestURI().indexOf("admin"));System.out.println("doFilterInternal-req"+req);System.out.println("doFilterInternal-req.getRequestURI()"+req.getRequestURI());//if(req.getRequestURI().indexOf("admin") == 1) {//chain.doFilter(req, res);//return;//}UsernamePasswordAuthenticationToken authentication = null;try {authentication = getAuthentication(req);System.out.println("doFilterInternal-authentication"+authentication);} catch (Exception e) {ResponseUtil.out(res, R.error());}if (authentication != null) {SecurityContextHolder.getContext().setAuthentication(authentication);} else {ResponseUtil.out(res, R.error());}/*将请求转发给过滤器链上下一个对象。这里的下一个指的是下一个filter,如果没有filter那就是你请求的资源。 一般filter都是一个链,web.xml 里面配置了几个就有几个。一个一个的连在一起 request -> filter1 -> filter2 ->filter3 -> .... -> request resource.*/chain.doFilter(req, res);}private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {// token置于header里String token = request.getHeader("token");if (token != null && !"".equals(token.trim())) {String userName = tokenManager.getUserFromToken(token);List permissionValueList = (List) redisTemplate.opsForValue().get(userName);Collection authorities = new ArrayList();for(String permissionValue : permissionValueList) {if(StringUtils.isEmpty(permissionValue)) continue;SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);authorities.add(authority);}if (!StringUtils.isEmpty(userName)) {return new UsernamePasswordAuthenticationToken(userName, token, authorities);}return null;}return null;}}
4.3.2登录过滤器 成功 获取token 保存redis
package com.atguigu.serurity.filter;import com.atguigu.commonutils.R;import com.atguigu.commonutils.ResponseUtil;import com.atguigu.serurity.entity.SecurityUser;import com.atguigu.serurity.entity.User;import com.atguigu.serurity.security.TokenManager;import com.fasterxml.jackson.databind.ObjectMapper;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.security.authentication.AuthenticationManager;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 org.springframework.security.web.util.matcher.AntPathRequestMatcher;import javax.servlet.FilterChain;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.util.ArrayList;/** * * 登录过滤器,继承UsernamePasswordAuthenticationFilter,对用户名密码进行登录校验 *
* * @author qy * @since 2019-11-08 */public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {private AuthenticationManager authenticationManager;private TokenManager tokenManager;private RedisTemplate redisTemplate;public TokenLoginFilter(AuthenticationManager authenticationManager, TokenManager tokenManager, RedisTemplate redisTemplate) {this.authenticationManager = authenticationManager;this.tokenManager = tokenManager;this.redisTemplate = redisTemplate;this.setPostOnly(false);this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login","POST"));}@Overridepublic Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)throws AuthenticationException {try {User user = new ObjectMapper().readValue(req.getInputStream(), User.class);return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new ArrayList()));} catch (IOException e) {throw new RuntimeException(e);}}/** * 登录成功 * @param req * @param res * @param chain * @param auth * @throws IOException * @throws ServletException */@Overrideprotected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain,Authentication auth) throws IOException, ServletException {SecurityUser user = (SecurityUser) auth.getPrincipal();String token = tokenManager.createToken(user.getCurrentUserInfo().getUsername());redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(), user.getPermissionValueList());ResponseUtil.out(res, R.ok().data("token", token));}/** * 登录失败 * @param request * @param response * @param e * @throws IOException * @throws ServletException */@Overrideprotected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,AuthenticationException e) throws IOException, ServletException {ResponseUtil.out(response, R.error());}}
4.4密码的处理方法类型
package com.atguigu.serurity.security;import com.atguigu.commonutils.MD5;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.stereotype.Component;/** * * t密码的处理方法类型 *
* * @author qy * @since 2019-11-08 */@Componentpublic class DefaultPasswordEncoder implements PasswordEncoder {public DefaultPasswordEncoder() {this(-1);}/** * @param strength *the log rounds to use, between 4 and 31 */public DefaultPasswordEncoder(int strength) {} /* * 密码加密 * */public String encode(CharSequence rawPassword) {return MD5.encrypt(rawPassword.toString());}/** 密码是否相等* */public boolean matches(CharSequence rawPassword, String encodedPassword) {return encodedPassword.equals(MD5.encrypt(rawPassword.toString()));}}
4.4退出
package com.atguigu.serurity.security;import com.atguigu.commonutils.R;import com.atguigu.commonutils.ResponseUtil;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.security.core.Authentication;import org.springframework.security.web.authentication.logout.LogoutHandler;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;/** * * 登出业务逻辑类 *
* * @author qy * @since 2019-11-08 */public class TokenLogoutHandler implements LogoutHandler {private TokenManager tokenManager;private RedisTemplate redisTemplate;public TokenLogoutHandler(TokenManager tokenManager, RedisTemplate redisTemplate) {this.tokenManager = tokenManager;this.redisTemplate = redisTemplate;}@Overridepublic void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {String token = request.getHeader("token");if (token != null) {tokenManager.removeToken(token);//清空当前用户缓存中的权限数据String userName = tokenManager.getUserFromToken(token);redisTemplate.delete(userName);}ResponseUtil.out(response, R.ok());}}
4.5token生成
package com.atguigu.serurity.security;import io.jsonwebtoken.CompressionCodecs;import io.jsonwebtoken.Jwts;import io.jsonwebtoken.SignatureAlgorithm;import org.springframework.stereotype.Component;import java.util.Date;/** * * token管理 *
* * @author qy * @since 2019-11-08 */@Componentpublic class TokenManager {private long tokenExpiration = 24*60*60*1000;private String tokenSignKey = "123456";public String createToken(String username) {String token = Jwts.builder().setSubject(username).setExpiration(new Date(System.currentTimeMillis() + tokenExpiration)).signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();return token;}public String getUserFromToken(String token) {String user = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject();return user;}public void removeToken(String token) {//jwttoken无需删除,客户端扔掉即可。}}
4.6未授权
package com.atguigu.serurity.security;import com.atguigu.commonutils.R;import com.atguigu.commonutils.ResponseUtil;import org.springframework.security.core.AuthenticationException;import org.springframework.security.web.AuthenticationEntryPoint;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;/** * * 未授权的统一处理方式 *
* * @author qy * @since 2019-11-08 */public class UnauthorizedEntryPoint implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {ResponseUtil.out(response, R.error());}}
4.7 XML
commontcom.atguigu0.0.1-SNAPSHOT4.0.0spring_securitycom.atguigucomment_utils0.0.1-SNAPSHOTorg.springframework.bootspring-boot-starter-securityio.jsonwebtokenjjwt
guli_parentcom.atguigu0.0.1-SNAPSHOT4.0.0commontpomservice_basecomment_utilsspring_securityorg.springframework.bootspring-boot-starter-data-redisorg.apache.commonscommons-pool22.6.0org.springframework.bootspring-boot-starter-webprovided com.baomidoumybatis-plus-boot-starterprovided org.projectlomboklombokprovided io.springfoxspringfox-swagger2provided io.springfoxspringfox-swagger-uiprovided org.springframework.bootspring-boot-starter-data-redis<!-- spring2.X集成redis所需common-pool2org.apache.commonscommons-pool22.6.0-->
4.7 jwt
package com.atguigu.commonutils;import io.jsonwebtoken.Claims;import io.jsonwebtoken.Jws;import io.jsonwebtoken.Jwts;import io.jsonwebtoken.SignatureAlgorithm;import org.springframework.http.server.reactive.ServerHttpRequest;import org.springframework.util.StringUtils;import javax.servlet.http.HttpServletRequest;import java.util.Date;/** * @author helen * @since 2019/10/16 * 生成字符串,包含用户信息, * jwt生成包含三部分:1头2.有效荷载3.签名哈希 防伪标志 */public class JwtUtils {public static final long EXPIRE = 1000 * 60 * 60 * 24;//token过期时间public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";//密钥public static String getJwtToken(String id, String nickname){String JwtToken = Jwts.builder()//第一步分.setHeaderParam("typ", "JWT").setHeaderParam("alg", "HS256")//第二部分.setSubject("guli-user").setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() + EXPIRE))//第三部分token主体.claim("id", id).claim("nickname", nickname).signWith(SignatureAlgorithm.HS256, APP_SECRET).compact();return JwtToken;}/** * 判断token是否存在与有效 * @param jwtToken * @return */public static boolean checkToken(String jwtToken) {if(StringUtils.isEmpty(jwtToken)) return false;try {Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);} catch (Exception e) {e.printStackTrace();return false;}return true;}/** * 判断token是否存在与有效 * @param request * @return */public static boolean checkToken(HttpServletRequest request) {try {String jwtToken = request.getHeader("token");System.out.println("jwtToken"+jwtToken);if(StringUtils.isEmpty(jwtToken)) return false;Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);} catch (Exception e) {e.printStackTrace();return false;}return true;}/** * 根据token获取会员id * @param request * @return */public static String getMemberIdByJwtToken(HttpServletRequest request) {String jwtToken = request.getHeader("token");if(StringUtils.isEmpty(jwtToken))return "";Jws claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);Claims claims = claimsJws.getBody();System.out.println("claims:"+claims);return (String)claims.get("id");}}
4.8 MD5
package com.atguigu.commonutils;import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;public final class MD5 {public static String encrypt(String strSrc) {try {char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8','9', 'a', 'b', 'c', 'd', 'e', 'f' };byte[] bytes = strSrc.getBytes();MessageDigest md = MessageDigest.getInstance("MD5");md.update(bytes);bytes = md.digest();System.out.println("bytes"+bytes);int j = bytes.length;System.out.println("j"+j);char[] chars = new char[j * 2];int k = 0;for (int i = 0; i >> 4 & 0xf];chars[k++] = hexChars[b & 0xf];}return new String(chars);} catch (NoSuchAlgorithmException e) {e.printStackTrace();throw new RuntimeException("MD5加密出错!!+" + e);}}public static void main(String[] args) {System.out.println(MD5.encrypt("111111"));}}
4.9 R
package com.atguigu.commonutils;import io.swagger.annotations.ApiModelProperty;import lombok.Data;import java.util.HashMap;import java.util.Map;@Datapublic class R {@ApiModelProperty(value = "是否成功")private Boolean success;@ApiModelProperty(value = "返回码")private Integer code;@ApiModelProperty(value = "返回消息")private String message;@ApiModelProperty(value = "返回数据")private Map data = new HashMap();//构造器私有化private R() {}//成功静态方法public static R ok() {R r = new R();r.setSuccess(true);r.setCode(ResultCode.SUCCESS);r.setMessage("成功");return r;}//失败静态方法public static R error() {R r = new R();r.setSuccess(false);r.setCode(ResultCode.ERROR);r.setMessage("失败");return r;}public R success(Boolean success) {this.setSuccess(success);return this;}public R message(String message) {this.setMessage(message);return this;}public R code(Integer code) {this.setCode(code);return this;}public R data(String key, Object value) {this.data.put(key, value);return this;}public R data(Map map) {this.setData(map);return this;}}