Springroo: использование логики двойной аутентификации: сессия и токен - PullRequest
1 голос
/ 08 марта 2019

Я делаю новое веб-приложение, которое разделено на две части:

  1. API + backoffice (часть Java)
  2. фронт клиента (часть JS)

Первый проект 1. это классический проект Springroo, использующий MVC и функции безопасности. Его цель - предоставить некоторый пользовательский интерфейс и маршруты администраторам, чтобы они управляли БД. Они аутентифицируются через классическую форму входа / прохода (автоматически генерируется Springroo), и аутентификация использует сеанс. Вот содержимое приложенияContext-security.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security" 
    xmlns:beans="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd">
    <!-- HTTP security configurations -->
    <http auto-config="true" use-expressions="true">
        <form-login login-processing-url="/resources/j_spring_security_check" login-page="/login" authentication-failure-url="/login?login_error=t"
        />
        <logout logout-url="/resources/j_spring_security_logout" />
        <!-- Configure these elements to secure URIs in your application -->
        <intercept-url pattern="/users/**" access="hasRole('ADM')" />
        <intercept-url pattern="/heroperiods/**" access="hasRole('USER')" />
        <intercept-url pattern="/karacters/**" access="hasRole('ADM')" />
        <intercept-url pattern="/karacterskillses/**" access="hasRole('ADM')" />
        <intercept-url pattern="/karactergpses/**" access="hasRole('ADM')" />

        <intercept-url pattern="/resources/**" access="permitAll" />
        <intercept-url pattern="/static/**" access="permitAll" />
        <intercept-url pattern="/login/**" access="permitAll" />
        <intercept-url pattern="/players/login" access="permitAll" />
        <intercept-url pattern="/players/new" access="permitAll" />
        <intercept-url pattern="/**" access="isAuthenticated()" />
        <!-- Concurrent Session Control -->
        <session-management session-authentication-error-url="/sessionExpired" >
            <concurrency-control max-sessions="1"/>
        </session-management>
    </http>
    <!-- Configure Authentication mechanism -->
    <beans:bean name="adminAuthenticationProvider" class="com.ksfadventure.security.AdminAuthenticationProvider">
    </beans:bean>

    <authentication-manager alias="authenticationManager">
        <authentication-provider ref="adminAuthenticationProvider" />
    </authentication-manager>
</beans:beans>

Эта часть использует AdminAuthenticationProvider, который расширяет AbstractUserDetailsAuthenticationProvider.

public class AdminAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails,
            UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        // TODO Auto-generated method stub      
    }

    @Override
    protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {

        String password = authentication.getCredentials().toString();
        AccessRight account = AccessRightController.getAccessRight(username, password);     
        List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>();
        if(null == account) 
            throw new BadCredentialsException("Wrong login or password");

        authList.add(new SimpleGrantedAuthority(account.getAccessRightRole()));

        return new User(username, account.getAccessRightPassword(), true, true, true, true, authList);
    }

}

Все это идет хорошо.

Теперь мне нужно добавить аутентификацию на основе маркеров без сохранения состояния (для входящего проекта JS-клиента), которая будет использоваться конечными пользователями.

@RequestMapping("/players")
@Controller
@RooWebScaffold(path = "players", formBackingObject = Player.class)
public class PlayerController {

    private TokenManager tokenManager = new TokenManager();

    // (...)

    /**
     * Authenticate a user
     */
    @RequestMapping(value="/login", method=RequestMethod.POST)
    public ResponseEntity<String> authenticate(@RequestBody String authParams) {
        ObjectMapper mapper = new ObjectMapper();
        // (...)
        WebServiceAnswer answer = new WebServiceAnswer();
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Type", "application/json; charset=utf-8");

        // JSON to Object
        try {
            player = mapper.readValue(authParams, Player.class);
            // (...)
        } catch (IOException e) {
            // (...)
        }
        if(null == player) return new ResponseEntity<String>(headers, HttpStatus.BAD_REQUEST);

        // get and check credentials
        email = player.getPlayerMail();
        password = player.getPlayerPassword();
        player = PlayerController.getPlayer(email, password);
        if (null == player) {
            // (...)
            return new ResponseEntity<String>(answer.toJsonString(), headers, HttpStatus.NOT_FOUND);
        }
        // generate token
        token = tokenManager.generate(player);
        // (...)
        return new ResponseEntity<String>(answer.toJsonString(), headers, HttpStatus.ACCEPTED);     
    }

    public static Player getPlayer(String email, String password) {
        // get the expected account
        Player player = Player.findPlayer(email);
        if (null == player) return null;
        // (...)
        String accountPassword = player.getPlayerPassword();
        // (...)
        // check password
        if (BCrypt.checkpw(password, accountPassword)) {
            // (...)
            return player;
        }
        // (...)
        return null;
    } // end of method getAccessRight

}

Вот класс TokenManager.

@Component
public class TokenManager {

    private final String secretKey = "ilovestackoverflow";

    public String generate(Player player) {
        Claims claims = Jwts.claims().setSubject(player.getPlayerMail());
        claims.put("playerId", String.valueOf(player.getPlayerId()));
        claims.put("role", Roles.getRoleUser());

        return Jwts.builder()
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS512, this.secretKey)
                .compact();
    }

    public Player validate(String token) {
        Player player = null;
        try {
            Claims body = Jwts.parser()
                    .setSigningKey(this.secretKey)
                    .parseClaimsJws(token)
                    .getBody();
            player = Player.findPlayer(body.getSubject());
            // maybe manage a role here ???
            // claims.put("role", jwtUser.getRole());
        }
        catch (Exception e) {
            System.out.println(e);
        }
        return player;
    }

}

Чтобы попытаться управлять маршрутами вызовов "/ Players / *" в качестве возможных маршрутов входа в систему, я создал класс TokenConfig.

//@EnableGlobalMethodSecurity(prePostEnabled=true)
@EnableWebSecurity
@Configuration
public class TokenConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private PlayerAuthenticationProvider playerAuthenticationProvider;

    @Bean
    public AuthenticationManager authenticationManager() {
        List<AuthenticationProvider> providerList = Collections.singletonList((AuthenticationProvider)playerAuthenticationProvider);
        ProviderManager pm = new ProviderManager(providerList);
        return pm;
    }

    //create a custom filter(this is ran at Tomcat start)
    @Bean
    public TokenFilter authTokenFilter() {
        TokenFilter filter = new TokenFilter();
        filter.setAuthenticationManager(authenticationManager());
        return filter;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
        .authorizeRequests().antMatchers("**/**").authenticated()
        .and()                  .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        http.addFilterBefore(authTokenFilter(), UsernamePasswordAuthenticationFilter.class);
        http.headers().cacheControl();
    }

}

Он использует PlayerAuthenticationProvider, который расширяет AbstractUserDetailsAuthenticationProvider.

@Component
public class PlayerAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

    @Autowired
    private TokenManager tokenManger;

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails,
            UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        // TODO Auto-generated method stub      
    }

    @Override
    protected UserDetails retrieveUser(String userMail, UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {            
        JwtAuthToken jwtAuthenticationToken = (JwtAuthToken) authentication;
        String token = jwtAuthenticationToken.getToken();
        Player account = tokenManger.validate(token);
        List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>();
        if(null == account) 
            throw new BadCredentialsException("Wrong login or password");

        authList.add(new SimpleGrantedAuthority(Roles.getRoleUser()));

        return new User(userMail, account.getPlayerPassword(), true, true, true, true, authList);
    }
}

И, наконец, TokenFilter.

public class TokenFilter extends AbstractAuthenticationProcessingFilter {

    public TokenFilter(String defaultFilterProcessesUrl) {
        super(defaultFilterProcessesUrl);
    }

    public TokenFilter() {
        super("/**");
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException, IOException, ServletException {
        String header = request.getHeader("Authorisation");

        if(header == null || !header.startsWith("Check ")) {
            throw new RuntimeException(" Token is missing");
        }

        String authenticationToken = header.substring(6);

        JwtAuthToken token = new JwtAuthToken(authenticationToken);
        return getAuthenticationManager().authenticate(token);
    }

}

Все это позволяет мне получить токен, когда я пытаюсь войти, используя POST-запрос к "/ Players / Login". Содержимое JSON возвращается, как и ожидалось, но проблема в том, что я не знаю, как управлять этим токеном для будущих вызовов клиента.

Если я пытаюсь достичь другого маршрута (например, / heroperiods / {i}), то сервер проверяет, что у меня нет активного (admin) сеанса, и запрашивает у меня аутентификацию.

Существует ли существующий процесс Java для управления токенами моих клиентов по каждому запросу? ИЛИ ЖЕ : Должен ли я открывать все маршруты и управлять ими по одному, вручную? (Я имею в виду проверку правильности токена) Если так, есть ли глобальное место, где я мог бы проверить статус токена для всех существующих маршрутов? Должен ли я хранить токен в моей таблице игроков и обновлять его + дату последнего использования?

[EDIT] Когда я отлаживаю свое приложение, метод TokenConfig.configure () никогда не используется. И если я добавлю аннотацию @EnableGlobalMethodSecurity (prePostEnabled = true), я получу исключение, поскольку вместо одного используется два параметра authenticationManager. [/ EDIT] * 1 038 *

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...