refactor: 新增API的请求拦截
This commit is contained in:
parent
3f384fbe79
commit
df7f6950d1
@ -0,0 +1,39 @@
|
||||
package com.agileboot.api.controller.app;
|
||||
|
||||
import com.agileboot.api.customize.service.JwtTokenService;
|
||||
import com.agileboot.common.core.base.BaseController;
|
||||
import com.agileboot.common.core.dto.ResponseDTO;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 调度日志操作处理
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/app")
|
||||
@AllArgsConstructor
|
||||
public class AppController extends BaseController {
|
||||
|
||||
private final JwtTokenService jwtTokenService;
|
||||
|
||||
/**
|
||||
* 访问首页,提示语
|
||||
*/
|
||||
@PreAuthorize("hasAuthority('annie')")
|
||||
@GetMapping("/list")
|
||||
public ResponseDTO<?> appLogin() {
|
||||
return ResponseDTO.ok();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package com.agileboot.api.controller.common;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import com.agileboot.api.customize.service.JwtTokenService;
|
||||
import com.agileboot.common.core.base.BaseController;
|
||||
import com.agileboot.common.core.dto.ResponseDTO;
|
||||
import java.util.Map;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 调度日志操作处理
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/common")
|
||||
@AllArgsConstructor
|
||||
public class LoginController extends BaseController {
|
||||
|
||||
private final JwtTokenService jwtTokenService;
|
||||
|
||||
/**
|
||||
* 访问首页,提示语
|
||||
*/
|
||||
@PostMapping("/app/{appId}/login")
|
||||
public ResponseDTO<String> appLogin() {
|
||||
String token = jwtTokenService.generateToken(MapUtil.of("token", "user1"));
|
||||
return ResponseDTO.ok(token);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
package com.agileboot.api.customize.config;
|
||||
|
||||
import com.agileboot.api.customize.service.JwtTokenService;
|
||||
import com.agileboot.infrastructure.user.app.AppLoginUser;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import java.io.IOException;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
/**
|
||||
* token过滤器 验证token有效性
|
||||
* 继承OncePerRequestFilter类的话 可以确保只执行filter一次, 避免执行多次
|
||||
* @author valarchie
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
@Autowired
|
||||
private JwtTokenService jwtTokenService;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||
String tokenFromRequest = jwtTokenService.getTokenFromRequest(request);
|
||||
|
||||
if (tokenFromRequest != null) {
|
||||
Claims claims = jwtTokenService.parseToken(tokenFromRequest);
|
||||
String token = (String) claims.get("token");
|
||||
// 根据token去查缓存里面 有没有对应的App用户
|
||||
// 没有的话 再去数据库中查询
|
||||
if (token != null && token.equals("user1")) {
|
||||
AppLoginUser loginUser = new AppLoginUser(23232323L, false, "dasdsadsds");
|
||||
loginUser.grantAppPermission("annie");
|
||||
UsernamePasswordAuthenticationToken suer1 = new UsernamePasswordAuthenticationToken(loginUser, null,
|
||||
loginUser.getAuthorities());
|
||||
SecurityContextHolder.getContext().setAuthentication(suer1);
|
||||
}
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,88 @@
|
||||
package com.agileboot.api.customize.config;
|
||||
|
||||
import com.agileboot.common.core.dto.ResponseDTO;
|
||||
import com.agileboot.common.exception.ApiException;
|
||||
import com.agileboot.common.exception.error.ErrorCode.Client;
|
||||
import com.agileboot.common.utils.ServletHolderUtil;
|
||||
import com.agileboot.common.utils.jackson.JacksonUtil;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
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.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
|
||||
/**
|
||||
* 主要配置登录流程逻辑涉及以下几个类
|
||||
|
||||
* @author valarchie
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||
@RequiredArgsConstructor
|
||||
public class SecurityConfig {
|
||||
/**
|
||||
* token认证过滤器
|
||||
*/
|
||||
@NonNull
|
||||
private JwtAuthenticationFilter jwtTokenFilter;
|
||||
|
||||
|
||||
/**
|
||||
* 跨域过滤器
|
||||
*/
|
||||
@NonNull
|
||||
private CorsFilter corsFilter;
|
||||
|
||||
|
||||
/**
|
||||
* 登录异常处理类
|
||||
* 用户未登陆的话 在这个Bean中处理
|
||||
*/
|
||||
@Bean
|
||||
public AuthenticationEntryPoint customAuthenticationEntryPoint() {
|
||||
return (request, response, exception) -> {
|
||||
ResponseDTO<Void> responseDTO = ResponseDTO.fail(
|
||||
new ApiException(Client.COMMON_NO_AUTHORIZATION, request.getRequestURI())
|
||||
);
|
||||
ServletHolderUtil.renderString(response, JacksonUtil.to(responseDTO));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
|
||||
httpSecurity.csrf().disable()
|
||||
// 不配这个错误处理的话 会直接返回403
|
||||
.exceptionHandling().authenticationEntryPoint(customAuthenticationEntryPoint())
|
||||
.and()
|
||||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 禁用 session
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
.antMatchers("/common/**").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
// 禁用 X-Frame-Options 响应头。下面是具体解释:
|
||||
// X-Frame-Options 是一个 HTTP 响应头,用于防止网页被嵌入到其他网页的 <frame>、<iframe> 或 <object> 标签中,从而可以减少点击劫持攻击的风险
|
||||
.headers().frameOptions().disable()
|
||||
.and()
|
||||
.formLogin().disable();
|
||||
|
||||
httpSecurity.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
// 添加CORS filter
|
||||
httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationFilter.class);
|
||||
|
||||
|
||||
return httpSecurity.build();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,129 @@
|
||||
package com.agileboot.api.customize.service;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.agileboot.common.constant.Constants.Token;
|
||||
import com.agileboot.common.exception.ApiException;
|
||||
import com.agileboot.common.exception.error.ErrorCode;
|
||||
import com.agileboot.infrastructure.cache.redis.RedisCacheService;
|
||||
import com.agileboot.infrastructure.user.web.SystemLoginUser;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.MalformedJwtException;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.SignatureException;
|
||||
import io.jsonwebtoken.UnsupportedJwtException;
|
||||
import java.util.Map;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* token验证处理
|
||||
*
|
||||
* @author valarchie
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
@Data
|
||||
@RequiredArgsConstructor
|
||||
public class JwtTokenService {
|
||||
|
||||
/**
|
||||
* 自定义令牌标识
|
||||
*/
|
||||
@Value("${token.header}")
|
||||
private String header;
|
||||
|
||||
/**
|
||||
* 令牌秘钥
|
||||
*/
|
||||
@Value("${token.secret}")
|
||||
private String secret;
|
||||
|
||||
@NonNull
|
||||
private RedisCacheService redisCache;
|
||||
|
||||
/**
|
||||
* 获取用户身份信息
|
||||
*
|
||||
* @return 用户信息
|
||||
*/
|
||||
public SystemLoginUser getLoginUser(HttpServletRequest request) {
|
||||
// 获取请求携带的令牌
|
||||
String token = getTokenFromRequest(request);
|
||||
if (StrUtil.isNotEmpty(token)) {
|
||||
try {
|
||||
Claims claims = parseToken(token);
|
||||
// 解析对应的权限以及用户信息
|
||||
String uuid = (String) claims.get(Token.LOGIN_USER_KEY);
|
||||
|
||||
return redisCache.loginUserCache.getObjectOnlyInCacheById(uuid);
|
||||
} catch (SignatureException | MalformedJwtException | UnsupportedJwtException | IllegalArgumentException jwtException) {
|
||||
log.error("parse token failed.", jwtException);
|
||||
throw new ApiException(jwtException, ErrorCode.Client.INVALID_TOKEN);
|
||||
} catch (Exception e) {
|
||||
log.error("fail to get cached user from redis", e);
|
||||
throw new ApiException(e, ErrorCode.Client.TOKEN_PROCESS_FAILED, e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 从数据声明生成令牌
|
||||
*
|
||||
* @param claims 数据声明
|
||||
* @return 令牌
|
||||
*/
|
||||
public String generateToken(Map<String, Object> claims) {
|
||||
return Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.signWith(SignatureAlgorithm.HS512, secret).compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从令牌中获取数据声明
|
||||
*
|
||||
* @param token 令牌
|
||||
* @return 数据声明
|
||||
*/
|
||||
public Claims parseToken(String token) {
|
||||
return Jwts.parser()
|
||||
.setSigningKey(secret)
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从令牌中获取用户名
|
||||
*
|
||||
* @param token 令牌
|
||||
* @return 用户名
|
||||
*/
|
||||
private String getUsernameFromToken(String token) {
|
||||
Claims claims = parseToken(token);
|
||||
return claims.getSubject();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求token
|
||||
*
|
||||
* @return token
|
||||
*/
|
||||
public String getTokenFromRequest(HttpServletRequest request) {
|
||||
String token = request.getHeader(header);
|
||||
if (StrUtil.isNotEmpty(token) && token.startsWith(Token.PREFIX)) {
|
||||
token = StrUtil.stripIgnoreCase(token, Token.PREFIX, null);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
package com.agileboot.api.customize.util;
|
||||
|
||||
public class ApiEncryptor {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,15 +1,17 @@
|
||||
package com.agileboot.infrastructure.user.base;
|
||||
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.hutool.extra.servlet.ServletUtil;
|
||||
import com.agileboot.common.utils.ServletHolderUtil;
|
||||
import com.agileboot.common.utils.ip.IpRegionUtil;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import eu.bitwalker.useragentutils.UserAgent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
/**
|
||||
@ -33,6 +35,7 @@ public class BaseLoginUser implements UserDetails {
|
||||
|
||||
protected String password;
|
||||
|
||||
protected List<GrantedAuthority> authorities = new ArrayList<>();
|
||||
/**
|
||||
* 登录信息
|
||||
*/
|
||||
@ -60,6 +63,10 @@ public class BaseLoginUser implements UserDetails {
|
||||
this.getLoginInfo().setLoginTime(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public void grantAppPermission(String appName) {
|
||||
authorities.add(new SimpleGrantedAuthority(appName));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
@ -107,7 +114,6 @@ public class BaseLoginUser implements UserDetails {
|
||||
* 是否可用 ,禁用的用户不能身份验证
|
||||
* 未实现此功能
|
||||
*/
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
@ -116,7 +122,7 @@ public class BaseLoginUser implements UserDetails {
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return ListUtil.empty();
|
||||
return authorities;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user