我正在尝试使用 Rest API 和 React 作为前端来实现 Spring Security,因为这是我的第一个全栈开发项目,我对如何实现正确的身份验证机制一无所知。
我搜索了很多并找到了关于 Spring Security with Basic Auth 的文章,但我无法弄清楚如何将该身份验证转换为 rest api,然后通过 session /cookies 进行相同的管理。即使我得到的任何 github 引用都非常旧,或者它们还没有完全迁移到 spring 安全 5。
所以无法找出确保休息 api 的正确方法。 (会不会就是spring security,spring security+jwt,spring security+jwt+spring session+cookie)
编辑
来自数据库的用户名验证
@Component
CustomUserDetailsService -> loadUserByUsername -> Mongo Db
通过加密
@Bean
public PasswordEncoder passwordEncoder() { ... }
跨源
@Bean
public WebMvcConfigurer corsConfigurer() { ... }
注册 Controller
@RestController
public class RegistrationController {
@PostMapping("/registration")
@ResponseStatus(HttpStatus.CREATED)
@ResponseBody
public ResponseEntity registerUserAccount(... ) { ... }
]
蒙戈 session
build.gradle
implementation 'org.springframework.session:spring-session-data-mongodb'
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
@Configuration
@EnableMongoHttpSession
所以以上是我已经实现的。在那之后,我被困在如何让用户保持 session 状态并继续验证用户。
最佳答案
基本授权:
(我假设您知道如何创建端点,并且您对创建简单的 Spring Boot 应用程序和 React 应用程序有基本的了解,所以我只会坚持授权主题。)
通过基本授权,您的前端应用程序必须在每次调用 API 时发送用户凭据。而且我们必须考虑到您的后端可能在 localhost:8080
上打开和前端 localhost:3000
所以我们必须处理 CORS。 (更多关于 CORS Cross-Origin Resource Sharing (CORS)
和 Spring Security 中的 CORS Spring Security CORS )
让我们从我们看到端点的安全配置开始。
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// by default uses a Bean by the name of corsConfigurationSource
.cors(withDefaults())
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/login").authenticated()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.antMatchers(HttpMethod.GET, "/cars").authenticated()
.anyRequest().authenticated()
.and()
.httpBasic();
}
//and cors configuration
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Collections.singletonList("http://localhost:3000"));
configuration.setAllowedHeaders(List.of("*"));
configuration.setAllowedMethods(Arrays.asList("GET","POST", "OPTIONS"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
我们有
/login
和 /cars
需要身份验证的端点。如果您运行后端应用程序并在 localhost:8080/login
上打开浏览器(或/cars
无关紧要)然后具有基本授权的窗口将在屏幕中间弹出。 Spring Security 中的默认用户名是 user
并在您的控制台中生成密码。复制粘贴密码它会通过。现在转到前端应用程序。假设我们有一些简单的应用程序,其中包含两个字段:用户名和密码以及按钮:登录。现在我们必须实现逻辑。
...
basicAuthorize = () => {
let username = this.state.username;
let password = this.state.password;
fetch("http://localhost:8080/login", {
headers: {
"Authorization": 'Basic ' + window.btoa(username + ":" + password)
}
}).then(resp => {
console.log(resp);
if (resp.ok) {
this.setState({
isLoginSucces: true});
} else {
this.setState({isLoginSucces: false});
}
return resp.text();
});
}
...
从顶部开始,我们有:
ok
我们可以将用户凭据存储在某个地方,并且在下次调用 API 时,我们必须再次包含授权 header 。 (但我们不应该将用户敏感数据存储在诸如 LocalStorage
或 SessionStorage
之类的地方,用于生产但用于开发是可以的 Storing Credentials in Local Storage )JWT:
什么是 JWT,您可以在本网站上阅读 Jwt.io .您还可以调试有助于乞讨的 token 。
制作身份验证端点和逻辑。
JWT 很难实现,因此创建一些有助于实现它的类会很有帮助。
像那里最重要的是:
username
的 POJO和 password
,只是为了从前端登录获取它并进一步发送。 @PostMapping("/authenticate")
public ResponseEntity<String> createJwtAuthenticationToken(@RequestBody JwtTokenRequest tokenRequest, HttpServletRequest request, HttpServletResponse response, TimeZone timeZone)
{
try
{
JwtTokenResponse accessToken = authenticationService.authenticate(tokenRequest, String.valueOf(request.getRequestURL()), timeZone);
HttpCookie accessTokenCookie = createCookieWithToken("accessToken", accessToken.getToken(), 10 * 60);
return ResponseEntity.ok().header(HttpHeaders.SET_COOKIE, accessTokenCookie.toString()).body("Authenticated");
}
catch (AuthenticationException e)
{
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage());
}
}
//creating cookie
private HttpCookie createCookieWithToken(String name, String token, int maxAge)
{
return ResponseCookie.from(name, token)
.httpOnly(true)
.maxAge(maxAge)
.path("/")
.build();
}
负责身份验证和 token 创建的服务
@Service
public class JwtAuthenticationService
{
private AuthenticationManager authenticationManager;
private final String SECRET_KEY = "SecretKey";
public JwtAuthenticationService(AuthenticationManager authenticationManager)
{
this.authenticationManager = authenticationManager;
}
public JwtTokenResponse authenticate(JwtTokenRequest tokenRequest, String url, TimeZone timeZone) throws AuthenticationException
{
UserDetails userDetails = managerAuthentication(tokenRequest.getUsername(), tokenRequest.getPassword());
String token = generateToken(userDetails.getUsername(), url, timeZone);
return new JwtTokenResponse(token);
}
管理身份验证。您不需要手动检查密码是否属于用户名,因为如果您有
loadByUsername
实现后,Spring 将使用此方法加载用户并检查密码。 Manually Authenticate User with Spring Securityprivate UserDetails managerAuthentication(String username, String password) throws AuthenticationException
{
Authentication authenticate = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
return (UserDetails) authenticate.getPrincipal();
}
如果没有抛出异常,则表示用户凭据正确,然后我们可以生成 JWT token 。
在这个例子中,我使用 Java JWT库,您可以添加到
pom.xml
文件。此方法根据请求的时区生成 token ,并存储信息请求 url。
private String generateToken(String username, String url, TimeZone timeZone)
{
try
{
Instant now = Instant.now();
ZonedDateTime zonedDateTimeNow = ZonedDateTime.ofInstant(now, ZoneId.of(timeZone.getID()));
Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY);
String token = JWT.create()
.withIssuer(url)
.withSubject(username)
.withIssuedAt(Date.from(zonedDateTimeNow.toInstant()))
.withExpiresAt(Date.from(zonedDateTimeNow.plusMinutes(10).toInstant()))
.sign(algorithm);
return token;
}
catch (JWTCreationException e)
{
e.printStackTrace();
throw new JWTCreationException("Exception creating token", e);
}
}
如果一切正常,则 token 存储在 http-only cookie 中。
当我们有 token 时,如果对经过身份验证的端点进行了请求,我们必须先过滤该请求。
我们需要添加我们的自定义过滤器:
public class JwtFilter extends OncePerRequestFilter
{
private final String SECRET_KEY = "SecretKey";
}
//or load from other source
public class JwtFilter extends OncePerRequestFilter
{
private final String SECRET_KEY = ApplicationConstants.SECRET_KEY;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException
{
Cookie tokenCookie = null;
if (request.getCookies() != null)
{
for (Cookie cookie : request.getCookies())
{
if (cookie.getName().equals("accessToken"))
{
tokenCookie = cookie;
break;
}
}
}
if (tokenCookie != null)
{
cookieAuthentication(tokenCookie);
}
chain.doFilter(request, response);
}
什么是 SecurityContextHolder 您可以在这里阅读 10.1. SecurityContextHolder
private void cookieAuthentication(Cookie cookie)
{
UsernamePasswordAuthenticationToken auth = getTokenAuthentication(cookie.getValue());
SecurityContextHolder.getContext().setAuthentication(auth);
}
private UsernamePasswordAuthenticationToken getTokenAuthentication(String token)
{
DecodedJWT decodedJWT = decodeAndVerifyJwt(token);
String subject = decodedJWT.getSubject();
Set<SimpleGrantedAuthority> simpleGrantedAuthority = Collections.singleton(new SimpleGrantedAuthority("USER"));
return new UsernamePasswordAuthenticationToken(subject, null, simpleGrantedAuthority);
}
private DecodedJWT decodeAndVerifyJwt(String token)
{
DecodedJWT decodedJWT = null;
try
{
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET_KEY))
.build();
decodedJWT = verifier.verify(token);
} catch (JWTVerificationException e)
{
//Invalid signature/token expired
}
return decodedJWT;
}
现在,请求使用 cookie 中的 token 进行过滤。我们必须在 Spring Security 中添加自定义过滤器:
@Override
protected void configure(HttpSecurity http) throws Exception
{
...
//now 'session' is managed by JWT http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(new JwtFilter(), UsernamePasswordAuthenticationFilter.class);
}
在前端,您没有太多工作。
在您的请求中,您只需添加
withCredentials: 'include'
,然后 cookie 将随请求一起发送。您必须使用 'include'
因为它是跨域请求。 Request.credentials示例请求:
fetch('http://localhost:8080/only-already-authenticated-users', {
method: "GET",
credentials: 'include',
headers: {
'Content-Type': 'application/json'
},
})
关于java - Spring Security with Rest API with React,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61441508/