【问题标题】:Understanding "Access Denied" in spring security with custom authentication使用自定义身份验证了解 Spring Security 中的“访问被拒绝”
【发布时间】:2019-01-31 14:34:49
【问题描述】:

我正在尝试掌握 Spring Security 和 经过相当多的工作(以及对 SO 的帮助),我能够在 Spring Security 中实现一些自定义身份验证机制,但现在我遇到了一些我不太了解的授权问题。

如果我向 localhost:8080/login?username=admin&password=sesamOeffneDich&secondSecret=youNeedMe 发出 POST 请求,我会收到 403 access denied.

如果有人能解释原因,我将不胜感激。

我的配置如下:

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

我认为使用这种配置,授权机制会自动授予访问权限,原因是 [...].hasAnyRole()[...]

这是我的令牌实现;为了练习目的,我想保持简单;如果需要理解实现的任何其他部分,请告诉我,我会提供它,但我不想一开始用不必要的代码过多地淹没帖子:

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

编辑:应要求我添加更多来源。

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

还有:

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

【问题讨论】:

    标签: java spring spring-security authorization


    【解决方案1】:

    为了处理您的问题,我需要 MyAuthenticationFilter 和 MyAuthenticationProvider 的来源。但是我有一个更好的解决方案,您可以在我的 github 上找到一个完全可行的配置了自定义身份验证和 RBAC(基于角色的访问控制)授权的 spring security 示例:https://github.com/mehditahmasebi/spring/tree/master/spring-boot

    希望对你有帮助。


    我的 WebConfig(或如您所说的 AppConfig)来源:

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

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

    和 LoginService 来源是:

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

    为了运行项目下载它并运行:

    mvnw spring-boot:run

    然后 --> http://localhost:8080/public/login/admin/admin

    那么你会得到你想要的成功结果。

    干杯。


    编辑:

    在使用添加的源更新问题后,我发现了问题。

    有一些解决方案:

    将 /** 更改为 /login

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

    或者以最简单的方式删除您的 antMatchers **:

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

    实际上如果你想要角色检查,在登录服务上检查角色不是一个好主意,最好是先用户登录,然后在进一步请求(其他特定的antMatcherUrl)角色检查。

    同时您可以使用@Secured 进行角色授权。

    为了安全保证,我在登录前添加了一个 Hello 服务,用于安全异常确定性,登录后一切正常。

    @RestController("/hello")
    public class HelloService {
    
        @GetMapping
        public String get()
        {
            return "Hello there!";
        }
    
    }
    

    【讨论】:

    • 非常感谢您链接进一步的参考!我仍然想知道我的误解在哪里以获得更好的总体理解。我添加了您要求的来源。
    • @Wolfone 我的荣幸。我更新了答案,请检查。
    • 谢谢,抱歉回复晚了!
    猜你喜欢
    • 1970-01-01
    • 2018-01-06
    • 2023-03-31
    • 1970-01-01
    • 2014-04-20
    • 2014-12-13
    • 2016-03-15
    • 2017-11-28
    • 2012-02-15
    相关资源
    最近更新 更多