主页 > 软件开发  > 

SpringCloudGateway整合SpringSecurity

SpringCloudGateway整合SpringSecurity

做了一个Spring Cloud项目,网关采用 Spring Cloud Gateway,想要用 Spring Security 进行权限校验,由于 Spring Cloud Gateway 采用 webflux ,所以平时用的 mvc 配置是无效的,本文实现了 webflu 下的登陆校验。

1. Security配置

这里先贴出配置类,方便了解大致情况。

其中涉及到的三个处理器均为自定义

package com.shop.jz.gateway.security.config; import com.shop.jz.gateway.security.constants.Constants; import com.shop.jz.gateway.security.handler.AuthenticationFailureHandler; import com.shop.jz.gateway.security.handler.AuthenticationSuccessHandler; import com.shop.jz.gateway.security.handler.ShopHttpBasicServerAuthenticationEntryPoint; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.server.SecurityWebFilterChain; /** * @author:JZ * @date:2020/5/21 */ @Slf4j @EnableWebFluxSecurity // 开启WebFluxSecurity,必须要添加 public class SecurityConfig { private String permitUrls = "/gateway/login1,/test1"; /** * 鉴权成功处理器 */ @Autowired private AuthenticationSuccessHandler authenticationSuccessHandler; /** * 登陆验证失败处理器 */ @Autowired private AuthenticationFailureHandler authenticationFailureHandler; /** * 未登录访问资源时的处理类,若无此处理类,前端页面会弹出登录窗口 */ @Autowired private ShopHttpBasicServerAuthenticationEntryPoint shopHttpBasicServerAuthenticationEntryPoint; @Bean public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity httpSecurity) { log.info("不进行权限校验url:{}", this.permitUrls); httpSecurity.authorizeExchange() .pathMatchers(this.permitUrls.split(",")).permitAll() .anyExchange().authenticated().and() .httpBasic().and() .formLogin().loginPage(Constants.LOGIN_URL) // 登陆地址 .authenticationSuccessHandler(authenticationSuccessHandler) // 设置鉴权成功处理器 .authenticationFailureHandler(authenticationFailureHandler) // 设置登陆验证失败处理器 .and().exceptionHandling().authenticationEntryPoint(shopHttpBasicServerAuthenticationEntryPoint) .and().csrf().disable() // 必须支持跨域 .logout().logoutUrl(Constants.LOGOUT_URL); // 退出登陆地址 return httpSecurity.build(); } // 密码加密方式 @Bean public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); } } 2. 自定义 UserDetails

在Security中用户信息需存放在 UserDetails 中,UserDetails 是一个接口,可以使用Security已经实现的 org.springframework.security.core.userdetails.User,也可以实现 UserDetails 接口自定义用户信息类。

package com.shop.jz.gateway.security.model; import com.jz.shop.user.dto.UserDto; import lombok.Data; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; import java.util.Set; /** * @author:JZ * @date:2020/5/17 */ @Data public class LoginUser implements UserDetails { /** * token */ private String token; /** * login time */ private Long loginTime; /** * expire time */ private Long expireTime; /** * Login IP address */ private String ip; /** * location */ private String location; /** * Browser type */ private String browser; /** * operating system */ private String os; /** * 用户名 */ private String userName; /** * 账号密码 */ private String userPwd; /** * 权限列表 */ private Set<String> permissions; public LoginUser() {} public LoginUser(String userName, String userPwd, Set<String> permissions) { this.userName = userName; this.userPwd = userPwd; this.permissions = permissions; } public LoginUser getLoginUser() { return this; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return null; } @Override public String getPassword() { return this.userPwd; } @Override public String getUsername() { return this.userName; } /** * Whether the account has not expired, which cannot be verified */ @Override public boolean isAccountNonExpired() { return true; } /** * Specifies whether the user is unlocked. Locked users cannot authenticate */ @Override public boolean isAccountNonLocked() { return true; } /** * Indicates whether the user's credentials (passwords) have expired, which prevents authentication */ @Override public boolean isCredentialsNonExpired() { return true; } /** * Available, disabled users cannot authenticate */ @Override public boolean isEnabled() { return true; } } 3. 自定义获取用户信息

在 WebFlux 中Security通过调用 ReactiveUserDetailsService 接口的实现类获取用户信息,与 MVC 中的 UserDetailsService 不同。

package com.shop.jz.gateway.security.service; import com.jz.shop mons.execptions.BaseException; import com.jz.shop.user.api.UserApi; import com.jz.shop.user.dto.UserDto; import com.shop.jz.gateway.security.model.LoginUser; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.ReactiveUserDetailsService; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; @Slf4j @Service public class ShopUserDetailsService implements ReactiveUserDetailsService { @Autowired private UserApi userApi; // 自定义实现的用户信息查询的feign接口 @Override public Mono<UserDetails> findByUsername(String username) { try { UserDto user = this.userApi.getUserInfoByUsername(username); LoginUser loginUser = new LoginUser(user.getUserName(), user.getPassword(), null); return Mono.just(loginUser); } catch (BaseException baseException) { log.warn(baseException.getMsg()); } return Mono.error(new UsernameNotFoundException("User Not Found")); } } 4. 鉴权成功处理器

当用户名和密码通过校验后会进入 WebFilterChainServerAuthenticationSuccessHandler ,我们可以重写 onAuthenticationSuccess 方法实现自定义返回信息

package com.shop.jz.gateway.security.handler; import com.alibaba.fastjson.JSON; import com.jz.shop mons.model.Result; import com.shop.jz.gateway.security.constants.Constants; import com.shop.jz.gateway.security.model.LoginUser; import com.shop.jz.gateway.security.service.TokenService; import com.shop.jz.gateway.security.utils.SecurityUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseCookie; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.security.core.Authentication; import org.springframework.security.web.server.WebFilterExchange; import org.springframework.security.web.server.authentication.WebFilterChainServerAuthenticationSuccessHandler; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.io.UnsupportedEncodingException; /** * 鉴权成功处理器 * @author:JZ * @date:2020/5/21 */ @Slf4j @Component public class AuthenticationSuccessHandler extends WebFilterChainServerAuthenticationSuccessHandler { @Autowired private TokenService tokenService; public AuthenticationSuccessHandler() {} @Override public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) { ServerWebExchange exchange = webFilterExchange.getExchange(); ServerHttpResponse response = exchange.getResponse(); log.info("用户:{} 登陆成功"); // 设置返回信息 HttpHeaders headers = response.getHeaders(); headers.add("Content-Type", "application/json; charset=UTF-8"); String responseJson = JSON.toJSONString(Result.success()); DataBuffer dataBuffer = null; try { dataBuffer = response.bufferFactory().wrap(responseJson.getBytes("UTF-8")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return response.writeWith(Mono.just(dataBuffer)); } } 5. 登陆验证失败处理器

当账号密码或权限验证异常时,会进入该处理器。

package com.shop.jz.gateway.security.handler; import com.alibaba.fastjson.JSON; import com.jz.shop mons.model.Result; import lombok.extern.slf4j.Slf4j; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpHeaders; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.server.WebFilterExchange; import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.io.UnsupportedEncodingException; /** * @author:JZ * @date:2020/5/21 */ @Slf4j @Component public class AuthenticationFailureHandler implements ServerAuthenticationFailureHandler { @Override public Mono<Void> onAuthenticationFailure(WebFilterExchange webFilterExchange, AuthenticationException e) { log.warn("鉴权失败"); ServerWebExchange exchange = webFilterExchange.getExchange(); ServerHttpResponse response = exchange.getResponse(); // 设置返回信息 HttpHeaders headers = response.getHeaders(); headers.add("Content-Type", "application/json; charset=UTF-8"); String responseJson = JSON.toJSONString(Result.fail("鉴权失败")); DataBuffer dataBuffer = null; try { dataBuffer = response.bufferFactory().wrap(responseJson.getBytes("UTF-8")); } catch (UnsupportedEncodingException ex) { ex.printStackTrace(); } return response.writeWith(Mono.just(dataBuffer)); } } 6. 未登录访问资源时的处理器 package com.shop.jz.gateway.security.handler; import com.alibaba.fastjson.JSONObject; import lombok.extern.slf4j.Slf4j; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.server.authentication.HttpBasicServerAuthenticationEntryPoint; import org.springframework.stereotype.Component; import org.springframework.util.Assert; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** * @author:JZ * @date:2020/5/21 */ @Slf4j @Component public class ShopHttpBasicServerAuthenticationEntryPoint extends HttpBasicServerAuthenticationEntryPoint { private static final String WWW_AUTHENTICATE = "WWW-Authenticate"; private static final String DEFAULT_REALM = "Realm"; private static String WWW_AUTHENTICATE_FORMAT = "Basic realm="%s""; private String headerValue = createHeaderValue("Realm"); public ShopHttpBasicServerAuthenticationEntryPoint() {} public void setRealm(String realm) { this.headerValue = createHeaderValue(realm); } private static String createHeaderValue(String realm) { Assert.notNull(realm, "realm cannot be null"); return String.format(WWW_AUTHENTICATE_FORMAT, new Object[]{realm}); } @Override public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException e) { ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(HttpStatus.UNAUTHORIZED); response.getHeaders().add("Content-Type", "application/json; charset=UTF-8"); response.getHeaders().set(HttpHeaders.AUTHORIZATION, this.headerValue); JSONObject result = new JSONObject(); result.put("code", "000000"); result.put("msg", "未登录鉴权"); DataBuffer dataBuffer = response.bufferFactory().wrap(result.toJSONString().getBytes()); return response.writeWith(Mono.just(dataBuffer)); } }
标签:

SpringCloudGateway整合SpringSecurity由讯客互联软件开发栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“SpringCloudGateway整合SpringSecurity