当前位置: 首页 > 知识库问答 >
问题:

使用restapi和React的Spring Security性

甄正信
2023-03-14

我试图用RESTAPI实现Spring Security性,并作为前端进行响应,因为这是我的第一个完整堆栈开发项目,我对如何实现正确的身份验证机制一无所知。

我已经搜索了很多,找到了关于Spring Security with Basic Auth的文章,但我无法弄清楚如何将该身份验证转换为其他api,然后通过会话/cookie进行管理。即使我得到的任何github引用都很旧,或者它们还没有完全迁移到Spring Security 5。

因此,我们无法找到保护RESTAPI的正确方法。(是否只是spring security、spring security jwt、spring security jwt和spring会话cookie)

编辑

来自数据库的用户名验证

@Component
CustomUserDetailsService -> loadUserByUsername -> Mongo Db 

密码传递

@Bean
public PasswordEncoder passwordEncoder() { ... }

交叉起源

@Bean
public WebMvcConfigurer corsConfigurer() { ... }

登记管理员

@RestController
public class RegistrationController {
@PostMapping("/registration")
@ResponseStatus(HttpStatus.CREATED)
@ResponseBody
public ResponseEntity registerUserAccount(... ) { ... }
]

Mongo会话

build.gradle
implementation 'org.springframework.session:spring-session-data-mongodb'
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'

@Configuration
@EnableMongoHttpSession

以上就是我已经实现的。在那之后,我一直在思考如何让用户保持会话状态,并不断从会话中验证用户。

共有2个答案

赵永逸
2023-03-14

对于我的第一个spring security fullstack项目,

我使用React作为前端,spring boot作为后端,spring security作为后端。

一些简单的启动步骤。

>

  • 在数据库中手动创建用户

    从react登录页面调用登录后api:

    const config={headers:{'Content Type':'application/x-www-form-urlencoded'};

    axios.post('http://localhost:9090/login',querystring.stringify( { username: username.value, password: password.value }),config).then(response => {
      setLoading(false);
      setUserSession(null, username.value);
    
      props.history.push('/landingpage');
    })
    

    就这样,你准备好了。

    有多种方法,这只是学习验证表单登录的一种简单方法

  • 张玺
    2023-03-14

    基本授权:

    (我假设你知道如何创建endpoint,并且你对创建简单的Spring Boot应用程序和反应应用程序有基本的了解,所以我将只坚持授权主题。)

    通过基本授权,您的前端应用程序必须在每次调用API时发送用户凭据。我们必须考虑到您的后端可能在localhost:8080和前端localhost:3000上打开,因此我们必须处理CORS。(有关CORS跨源资源共享(CORS)和Spring Security中的CORS的更多信息Spring Security CORS)

    让我们从看到endpoint的安全配置开始。

     @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;
        }
    

    我们有需要身份验证的endpoint。如果您运行后端应用程序并在localhost:8080/login(或/car无所谓)上打开浏览器,则具有基本授权的窗口将在屏幕中间弹出。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();
                });
        }
    ...
    

    从顶部开始,我们有:

    1. 用户凭据
    2. 根据MDN web docks授权标头上的基本授权规范进行授权的标头
    3. 如果响应是ok,我们可以将用户凭据存储在某个地方,并且在下次调用API时,我们必须再次包含授权头。(但我们不应将用户敏感数据存储在适当的位置,如LocalStorageSessionStorage,用于生产,但用于开发,在本地存储中存储凭据是可以的)

    JWT:

    什么是JWT你可以在这个网站上阅读JWT。木卫一。您还可以调试在乞讨时有用的令牌。

    创建身份验证endpoint和逻辑
    JWT很难实现,因此创建一些类来帮助实现这一点很有帮助。

    最重要的是:

    • JwtTokenRequest令牌请求-这是POJO与用户名密码,只是从前端登录并进一步发送它。
    • JwtToken响应,也是POJO,只是在cookie中发送的令牌字符串
    • 我还让TimeZone设置令牌过期。
    @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();
        }
    

    负责身份验证和令牌创建的服务

    @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将使用此方法加载用户并检查密码。使用Spring Security手动验证用户

    private UserDetails managerAuthentication(String username, String password) throws AuthenticationException
        {
            Authentication authenticate = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
    
            return (UserDetails) authenticate.getPrincipal();
        }
    

    若并没有抛出异常,这意味着用户凭证是正确的,那个么我们可以生成JWT令牌。

    在这个例子中,我使用JavaJWT库,您可以将其添加到pom.xml文件中。

    该方法根据请求的时区生成令牌,并存储请求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);
            }
        }
    

    若一切正常,则令牌存储在仅http的cookie中。

    当我们拥有令牌时,如果请求是对经过身份验证的endpoint执行的,那么我们必须在之前过滤该请求。我们需要添加自定义过滤器

    • 首先扩展过滤器(你可以在这里阅读为什么这个什么是OncePerrecestFilter?)
    • 添加键
    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中设置该用户已通过身份验证。安全配置
    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中的令牌进行过滤。我们必须在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);
        }
    


    在你的请求中,你只需要添加with凭据:'包括',然后cookie将随请求一起发送。您必须使用'包括',因为它是跨源请求。Request.credentials

    请求示例

    fetch('http://localhost:8080/only-already-authenticated-users', {
          method: "GET",
          credentials: 'include',
          headers: {
            'Content-Type': 'application/json'
          },
        })
    
     类似资料:
    • 我正在学习springsecurity(基于java的配置),我无法使注销正常工作。当我点击注销时,我看到URL更改为http://localhost:8080/logout并获取“HTTP 404-/logout”。登录功能工作正常(即使使用自定义登录表单),但问题是注销,我怀疑重定向的url“localhost:8080/logout”应该类似于“localhost:8808/springte

    • 我正试图将SpringSecurity5.1.4.RELEASE集成到一个已经在运行的JSF2.2-PrimeFaces6.1应用程序中,以实现安全性。当我尝试访问受保护的页面“logged.xhtml”时,spring会触发并将我带到登录页面“login.xhtml”,因此spring似乎工作正常。 问题是,一旦我配置了Spring,所有Primeface都停止工作(以及其他Primeface组

    • 我在这里有很多问题要解决。一直试图将上述3项技术集成到我们的WebApp中…我们要使用 null web.xml: 我的servlet-context.xml: My manager-context.xml: 编辑2 我认为主要的问题是SpringSecurity需要webapp上下文(ContextLoaderListener)才能工作,但web应用程序是在servlet上下文中运行的。控制器方

    • 我使用已签名的URL从使用Air的移动设备上传blob。 我有两个问题: 使用签名 URL 上传 Blob 时,我假设我不需要按照文档中所述包含所有标头。我是否认为我只需要对URL执行请求,并将编码到正文中的文件包含在正文中,设置为? http://msdn . Microsoft . com/en-us/library/windows azure/DD 179451 . aspx (Upload

    • 问题内容: 我是React-Redux的新技术,希望在某些实现方面对您有所帮助。 我想用套接字(socket.io)实现一个聊天应用程序。首先,用户必须注册(我在服务器端使用通行证),然后,如果注册成功,则用户必须连接到webSocket。 我认为最好的办法是对所有操作使用管道之类的中间件,并根据获取中间件的操作类型来执行不同的操作。 如果操作类型为,则创建客户端-服务器连接并设置所有将来自服务器

    • 问题内容: 使用 withRouter() 时如何获取路由上下文,位置,参数等? 您可以使用withRouter获得该信息,还是必须将这些内容显式传递到组件树中? 问题答案: 因此,不再使用。在道具中都可以使用: 因此,假设您想将用户转到新路线,您只需