如何在Spring Boot中使用JWT身份验证实现基本身份验证?

问题描述:

我已经构建了一个 Spring-Boot 应用程序,它可以使用 jwt 身份验证。

I have built a Spring-Boot application that works with jwt authentication.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.diplie</groupId>
    <artifactId>rest-api</artifactId>
    <version>1.0.0</version>
    <packaging>war</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.3.0.RC1</version>
    </parent>

    <properties>
        <springfox-version>2.2.2</springfox-version>
        <java.version>1.8</java.version>
        <maven.test.skip>true</maven.test.skip>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>LATEST</version>
        </dependency>

        <!-- Swagger 2 -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>${springfox-version}</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>${springfox-version}</version>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
                <version>${project.parent.version}</version>
                <scope>provided</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>repository.springsource.milestone</id>
            <name>SpringSource Milestone Repository</name>
            <url>http://repo.springsource.org/milestone</url>
        </repository>
    </repositories>

    <pluginRepositories>
        <pluginRepository>
            <id>repository.springsource.milestone</id>
            <name>SpringSource Milestone Repository</name>
            <url>http://repo.springsource.org/milestone</url>
        </pluginRepository>
    </pluginRepositories>

</project>

我想要基本身份验证,当我使用 Swagger $ c时$ c>当我点击试用按钮

I want have a basic authentication, when I use Swagger I want to have a popup with when I click on the Try Out button

例如:

For example:

如何在同一端点上使用Spring安全性的两个安全性(表单库,JWT令牌)过滤器?

how can use two security(form base,JWT token) filters of spring security on same endpoint?

WebSecurityConfig

WebSecurityConfig

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.exceptionHandling().and().anonymous().and().servletApi().and().authorizeRequests()

                // Allow anonymous resource requests
                .antMatchers("/swagger-ui.html").permitAll().antMatchers("/").permitAll()
                .antMatchers("/webjars/springfox-swagger-ui/**").permitAll().antMatchers("/swagger-resources/**")
                .permitAll().antMatchers("/v2/api-docs").permitAll().antMatchers("/favicon.ico").permitAll()
                .antMatchers("**/*.html").permitAll().antMatchers("**/*.css").permitAll().antMatchers("**/*.js")
                .permitAll()

                // Allow anonymous logins
                .antMatchers("/user/User").permitAll().antMatchers("/locality/**").hasAuthority("Admin")
                .antMatchers("/category/**").hasAuthority("Admin").antMatchers("/item").hasAuthority("Item")
                .antMatchers("/item/userItems").hasAuthority("Item").antMatchers("item/lookFor").permitAll()
                .antMatchers("item/items").hasAuthority("User")

                // All other request need to be authenticated
                .anyRequest().authenticated().and()
                // And filter other requests to check the presence of JWT in
                // header
                .addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        // Créer un compte par défaut
        auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN");
    }
}

TokenAuthenticationService

TokenAuthenticationService

public class TokenAuthenticationService {

    static ResourceBundle bundle = ResourceBundle.getBundle("application");

    static void addAuthentication(HttpServletResponse res, String username) {

        String JWT = Jwts.builder().setSubject(username)
                .setExpiration(new Date(System.currentTimeMillis() + getExpirationTime()))
                .signWith(SignatureAlgorithm.HS512, getSecret()).compact();
        res.addHeader(getHeaderString(), getTokenPrefix() + " " + JWT);
    }

    static Authentication getAuthentication(HttpServletRequest request) {
        String token = request.getHeader(getHeaderString());
        if (token != null) {
            // Analyse du jeton.
            String user = Jwts.parser().setSigningKey(getSecret()).parseClaimsJws(token.replace(getTokenPrefix(), ""))
                    .getBody().getSubject();
            return user != null ? new UsernamePasswordAuthenticationToken(user, null, emptyList()) : null;
        }
        return null;
    }

    /**
     * @return the secret
     */
    public static String getSecret() {
        return bundle.getString("secret");
    }

    /**
     * @return the expirationTime
     */
    public static long getExpirationTime() {
        return Long.valueOf(bundle.getString("expiration.time"));
    }

    /**
     * @return the tokenPrefix
     */
    public static String getTokenPrefix() {
        return bundle.getString("token.prefix");
    }

    /**
     * @return the headerString
     */
    public static String getHeaderString() {
        return bundle.getString("header.string");
    }

}

JWTLoginFilter

JWTLoginFilter

public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter {

    public JWTLoginFilter(String url, AuthenticationManager authManager) {
        super(new AntPathRequestMatcher(url));
        setAuthenticationManager(authManager);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
            throws AuthenticationException, IOException, ServletException {
        AccountCredentials creds = new ObjectMapper().readValue(req.getInputStream(), AccountCredentials.class);
        return getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken(creds.getUsername(),
                creds.getPassword(), Collections.emptyList()));
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain,
            Authentication auth) throws IOException, ServletException {
        TokenAuthenticationService.addAuthentication(res, auth.getName());
    }
}

JWTAuthenticationFilter

JWTAuthenticationFilter

public class JWTAuthenticationFilter extends GenericFilterBean {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws IOException, ServletException {
        Authentication authentication = TokenAuthenticationService.getAuthentication((HttpServletRequest) request);

        SecurityContextHolder.getContext().setAuthentication(authentication);
        filterChain.doFilter(request, response);
    }
}

AccountCredentials

AccountCredentials

public class AccountCredentials {

    private String username;
    private String password;

    /**
     * 
     */
    public AccountCredentials() {
        super();
    }

    /**
     * @return the username
     */
    public String getUsername() {
        return username;
    }

    /**
     * @param username
     *            the username to set
     */
    public void setUsername(String username) {
        this.username = username;
    }

    /**
     * @return the password
     */
    public String getPassword() {
        return password;
    }

    /**
     * @param password
     *            the password to set
     */
    public void setPassword(String password) {
        this.password = password;
    }

}


您必须使用不同的根URL创建两个不同的 WebSecurityConfigurerAdapter 配置。如果URL重叠(即 / admin和/ ** ),则需要使用 @Order 来定义优先级关于配置的注释。

You will have to create two different WebSecurityConfigurerAdapter configurations with different root URLs. If the URLs overlap (ie /admin and /**) then you will need to define priority by using @Order annotation on the configuration.

这是一个基于HTTP Basic和Form的身份验证的工作示例。

Here's a working example for HTTP Basic and Form based authentication.

https://github.com/ConsciousObserver/TestMultipleLoginPagesFormAndBasic.git

package com.test;

import javax.servlet.http.HttpSession;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@SpringBootApplication
public class TestMultipleLoginPagesApplication {

    public static void main(String[] args) {
        SpringApplication.run(TestMultipleLoginPagesApplication.class, args);
    }
}

@Controller
class MvcController {
    @RequestMapping(path="form/formLogin", method=RequestMethod.GET)
    public String formLoginPage() {
        return "formLogin";
    }

    @RequestMapping(path="form/formHome", method=RequestMethod.GET)
    public String formHomePage() {
        return "formHome";
    }

    @RequestMapping(path="basic/basicHome", method=RequestMethod.GET)
    public String userHomePage() {
        return "basicHome";
    }

    @RequestMapping(path="basic/logout", method=RequestMethod.GET)
    public String userLogout(HttpSession session) {
        session.invalidate();
        return "basicLogout";
    }
}

@Configuration
@Order(1)
class FormSecurity extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/form/**")
            .authorizeRequests()
                .anyRequest().hasRole("FORM_USER")
            .and()
            .formLogin()
                .loginPage("/form/formLogin").permitAll()
                .loginProcessingUrl("/form/formLoginPost").permitAll()
                .defaultSuccessUrl("/form/formHome")
            .and()
                .logout().logoutUrl("/form/logout").logoutSuccessUrl("/form/formLogin")
            .and()
            .httpBasic().disable()
            .csrf().disable();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("user")
            .password("test")
            .roles("FORM_USER");
    }
}

@Configuration
@Order(2)
class BasicAuthSecurity extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/basic/**")
            .authorizeRequests()
            .anyRequest().hasRole("BASIC_USER")
            .antMatchers("/basic/logout").permitAll()
            .and()
                .httpBasic()
            .and()
                .csrf().disable();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("basic_user")
            .password("test")
            .roles("BASIC_USER");
    }
}

@Configuration
@Order(3)
class RootUrlSecurity extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        /*
         * Put any security expectations from the root URL here, currently everything is permitted.
         * Since it's the last in the order /form/** and /basic/** have a priority over it.
         */
        http.antMatcher("/**")
            .authorizeRequests()
                .anyRequest().permitAll();
    }
}

注意:由于这些登录页面不是来自不同的应用程序,它们共享SecurityContextHolder或安全上下文。因此,如果您从一个登录页面登录然后尝试转到另一个登录页面的受保护资源,则不会将您重定向到下一个登录页面。相反,您将获得403(取决于不同登录页面分配的角色)。一次只能维护一个登录会话。

Note: Since these login pages are not from different applications, they share the SecurityContextHolder or the security context. So if you login from one login page and then try to go the protected resource of the other, you won't be redirected to the next login page. Instead you'll get the 403 (depending on the roles assigned by the different login pages). At a time only one login session can be maintained.