简体   繁体   中英

Understanding “Access Denied” in spring security with custom authentication

I am trying to get a grasp of Spring Security and after quite some work (and help on SO) I was able to implement some custom authentication mechanism in spring security but now I have some authorization issue I don't really understand.

If I make a POST-request to localhost:8080/login?username=admin&password=sesamOeffneDich&secondSecret=youNeedMe I receive 403 access denied.

If someone could explain why, I would be thankful.

My configuration is as follows:

@Configuration
@EnableWebSecurity
@EnableWebMvc
@ComponentScan
public class AppConfig extends WebSecurityConfigurerAdapter
{
    @Autowired
    MyAuthenticationProvider myAuthenticationProvider;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(myAuthenticationProvider);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception
    {
        http.addFilterBefore(new MyAuthenticationFilter(authenticationManager()), BasicAuthenticationFilter.class)
                .authorizeRequests().antMatchers("/**")
                    .hasAnyRole()
                    .anyRequest()
                    .authenticated()
                    .and()
                .csrf().disable()
                .httpBasic().disable();
    }

    @Bean
    public AuthenticationManager authenticationManager(){
        return new ProviderManager(Arrays.asList(myAuthenticationProvider));
    }

    @Bean
    public ViewResolver viewResolver()
    {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/views/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }
}

I thought with this configuration the authorization-mechanism would automatically grant access due to [...].hasAnyRole()[...] .

This is my token-implementation; I wanted to keep it simple for practice purposes; if any other part of the implementation is needed to understand please tell me and I will provide it but I didn't want to initially flood the post with unnecessary code too much:

public class MyAuthenticationToken implements Authentication {
    public static final String SECOND_SECRET = "youNeedMe";
    private final String principalName;
    private MyCredentials credentials;
    private boolean authenticated;

    public MyAuthenticationToken(String principalName, MyCredentials credentials) {
        this.principalName = principalName;
        this.credentials = credentials;
    }
    //Everyone is admin for the sake of the example
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        LinkedList<GrantedAuthority> authorities = new LinkedList<>();
        authorities.add(new SimpleGrantedAuthority("ADMIN"));
        return authorities;
    }

    @Override
    public Object getCredentials() {
        return this.credentials;
    }

    @Override
    public Object getDetails() {
        return null;
    }

    @Override
    public Object getPrincipal() {
        return this.principalName;
    }

    @Override
    public boolean isAuthenticated() {
        return this.authenticated;
    }

    @Override
    public void setAuthenticated(boolean b) throws IllegalArgumentException {
        this.authenticated = b;
    }

    @Override
    public String getName() {
        return this.principalName;
    }
}

EDIT: on request I add further sources.

public class MyAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    public MyAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(new AntPathRequestMatcher("/login", "POST"));
        this.setAuthenticationManager(authenticationManager);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
            if (!request.getMethod().equals("POST")) {
                throw new AuthenticationServiceException(
                        "Authentication method not supported: " + request.getMethod());
            }

            String username = request.getParameter("username");
            String password = request.getParameter("password");
            String secondSecret = request.getParameter("secondSecret");

            if (username == null) {
                username = "";
            }

            if (password == null) {
                password = "";
            }

            username = username.trim();

            MyAuthenticationToken authRequest = new MyAuthenticationToken(username, new MyCredentials(password, secondSecret));

            return this.getAuthenticationManager().authenticate(authRequest);
    }
}

And:

@Component
public class MyAuthenticationProvider implements AuthenticationProvider {
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        MyAuthenticationToken myAuthenticationToken = (MyAuthenticationToken) authentication;
        MyCredentials credentials = (MyCredentials) myAuthenticationToken.getCredentials();
        if (credentials.getPassword().equals("sesamOeffneDich") && credentials.getSecondSecret().equals(MyAuthenticationToken.SECOND_SECRET)){
            myAuthenticationToken.setAuthenticated(true);
            return myAuthenticationToken;
        }else{
            throw new BadCredentialsException("Bad credentials supplied!");
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return MyAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

For processing your question I need the source of MyAuthenticationFilter and MyAuthenticationProvider. But I have a better solution that implemented before, you can found a fully workable example of spring security configured with Custom Authentication and RBAC (Role-based Access Control) Authorization at my github ready for you : https://github.com/mehditahmasebi/spring/tree/master/spring-boot

I hope this will help you.


My WebConfig (or as you said AppConfig) Sources :

@Configuration
@EnableWebMvc
@EnableWebSecurity
@EnableGlobalMethodSecurity(
        prePostEnabled=true,
        securedEnabled=true,
        jsr250Enabled=true)
public class WebConfig extends WebSecurityConfigurerAdapter implements WebMvcConfigurer {

    @Override
    protected void configure(HttpSecurity http) throws Exception {      
        http
            .authorizeRequests()
                .antMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
            .and().csrf().disable();
    }

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
        .allowCredentials(true)
        .allowedHeaders("*")
        .allowedMethods("GET, POST, PATCH, PUT, DELETE, OPTIONS")
        .allowedOrigins("*");
    }

    @Bean
    public InternalResourceViewResolver jspViewResolver() {
        InternalResourceViewResolver resolver= new InternalResourceViewResolver();
        resolver.setPrefix("/jsp/");
        resolver.setSuffix(".jsp");
        return resolver;
    }  

    @Bean
    public ErrorPageFilter errorPageFilter() {
        return new ErrorPageFilter();
    }
}

and source of CustomAuthentication :

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {

    public CustomAuthenticationProvider() {
        super();
    }

    // API

    @Override
    public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
        final String name = authentication.getName();
        final String password = authentication.getCredentials().toString();
        final List<GrantedAuthority> grantedAuths = new ArrayList<>();

        if(!(name.equals("admin") && password.equals("admin")) && 
                !(name.equals("user") && password.equals("user")))
            throw new RuntimeException("username or password is incorrect");
        if(name.equals("admin"))
        {
            grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
            grantedAuths.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
        }
        else
            grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));

        final UserDetails principal = new User(name, password, grantedAuths);
        final Authentication auth = new UsernamePasswordAuthenticationToken(principal, password, grantedAuths);
        return auth;

    }

    @Override
    public boolean supports(final Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }

}

and LoginService source is :

@RestController
@RequestMapping("/public/login")
public class LoginService {

    @Autowired
    CustomAuthenticationProvider provider;

    @GetMapping
    public String loginTest(){
        return "Login OK";
    }

    @GetMapping("/{username}/{password}")
    public ResponseEntity<Map<String, String>> login(@PathVariable String username,@PathVariable String password ,HttpServletRequest request)
    {
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
        Authentication authenticate = provider.authenticate(token);
        SecurityContextHolder.getContext().setAuthentication(authenticate);
        request.getSession().setAttribute("username", username);
        Map<String, String> result = new HashMap<>();
        result.put(username, new String(Base64.getEncoder().encode(password.getBytes())));
        ResponseEntity<Map<String, String>> finalResult = new ResponseEntity<>(result,HttpStatus.OK);
        return finalResult;
    }
}

For running project download it and run :

mvnw spring-boot:run

and then --> http://localhost:8080/public/login/admin/admin

then you will got successful result as you want.

Cheers.


EDIT :

After updating question with added source, I found problem.

There is some solution :

change /** to /login

    http.addFilterBefore(new MyAuthenticationFilter(authenticationManager()), BasicAuthenticationFilter.class)
            .authorizeRequests()
                .antMatchers("/login").hasAnyRole()
                .anyRequest().authenticated()
            .and()
            .csrf().disable()
            .httpBasic().disable();

Or in simplest way remove your antMatchers ** :

    http.addFilterBefore(new MyAuthenticationFilter(authenticationManager()), BasicAuthenticationFilter.class)
            .authorizeRequests()
                .anyRequest().authenticated()
            .and()
            .csrf().disable()
            .httpBasic().disable();

Actually if you want Role checking, it's not a good idea check ROLE on login service, it's better a user login first and then in further request (other specific antMatcherUrl) role checked.

In the same time you can use @Secured for role authorization.

For security assurance I added a Hello service for security exception certainty before login, and after login everything goes fine.

@RestController("/hello")
public class HelloService {

    @GetMapping
    public String get()
    {
        return "Hello there!";
    }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM