Spring Security, Method Security annotation (@Secu

2020-05-23 02:22发布

问题:

I am trying to set up a method security annotation using @Secured("ADMIN") (without any XML, only java config, Spring Boot). But access via roles does not work.

Security Config:

@Configuration
@EnableWebSecurity
public class AppSecurityConfiguration extends WebSecurityConfigurerAdapter{

.....

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/api/**").fullyAuthenticated().and()
                .addFilterBefore(tokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }

.....

}

I want restrict access to the method of the controller:

@RestController
@RequestMapping("/api/groups")
public class GroupController {

    @Autowired
    private GroupService groupService;

    @Secured("ADMIN")
    @RequestMapping
    public List<Group> list() {
        return groupService.findAll();
    }

}

Restrict access by the url is working, with:

.antMatchers("/api/**").hasAuthority("ADMIN")

Maybe I forgot to specify that I want restrict by roles?

UPD: By the rules, At what layer must be @PreAuthorize("hasRole('ADMIN')") in Controller layer or in Service layer?

回答1:

Kindly add this

@EnableGlobalMethodSecurity(securedEnabled = true)

This element is used to enable annotation-based security in your application (by setting the appropriate attributes on the element), and also to group together security pointcut declarations which will be applied across your entire application context specifically for @Secured. Hence your code should look like this

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class AppSecurityConfiguration extends WebSecurityConfigurerAdapter{..


回答2:

There may be many reasons for which method security on a controller does not work.

First because it is never cited as example in Spring Security manual ... joking but it may be tricky to take Spring tools where they do not want to go.

More seriously, you should enable method security as already said by @Mudassar. The manual says :

We can enable annotation-based security using the @EnableGlobalMethodSecurity annotation on any @Configuration instance. For example, the following would enable Spring Security’s @Secured annotation.

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true)
public class MethodSecurityConfig {
   // ...
}

Note that Mudassar's answer is correct till here.

But method security is based on AOP, which by default uses JDK proxying on interfaces. That's the reason why all examples applies method security on the service layer, because the service classes are normally injected in controllers as interfaces.

You can of course use it on controller layer, but :

  • either all your controllers implement interfaces for you all @Secured annotated methods
  • or you must switch to class proxying

The rule that I try to follow is :

  • if I want to secure an URL, I stick to HTTPSecurity
  • if I need to allow finer grained access, I add security at service layer


回答3:

I know this thread is quite old and my answer alludes to portions of the answers by various people in this thread; but here is a list combined list of pitfalls and answers:

  1. When using @Secured, and the role name is (e.g.) ADMIN; this means an annotation of @Secured("ROLE_ADMIN").
  2. WebSecurityConfigurerAdapter must have @EnableGlobalMethodSecurity(securedEnabled = true)
  3. As with most Spring related proxies, make sure that the class and the secured methods are not in any way final. For Kotlin this means "open" every method as well as the class.
  4. When the class and its methods are virtual ("open"), then there is no implied need for an interface.

Here is part of a working Kotlin example:

@RestController
@RequestMapping("api/v1")

    open class DiagnosticsController {
        @Autowired
        lateinit var systemDao : SystemDao

        @RequestMapping("ping", method = arrayOf(RequestMethod.GET))
        @Secured("ROLE_ADMIN")
        open fun ping(request : HttpServletRequest, response: HttpServletResponse) : String { ... 
    }

and

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
open class WebSecurityConfig : WebSecurityConfigurerAdapter() {

Regards



回答4:

This issue was solved.

I add @EnableGlobalMethodSecurity(prePostEnabled = true)

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class AppSecurityConfiguration extends WebSecurityConfigurerAdapter{
}

And in controller i changed @Secured("ADMIN") to @PreAuthorize("hasRole('ADMIN')")



回答5:

I want to share my decision, may be it will be helpful.

Used spring mvc + spring security, version 4.2.9.RELEASE

For example, i have a Service with method annotated @Secured

@Secured("ACTION_USER_LIST_VIEW")
List<User> getUsersList();

But, it didn't work, because GlobalMethodSecurityConfiguration has inside method.

protected AccessDecisionManager accessDecisionManager()

in which initialized the new RoleVoter() with default rolePrefix = "ROLE_"; (this makes it impossible to use beans to set your rolePrefix) that give to us not working annotations, because RoleVoter expects annotation value which starts with 'ROLE_'

For resolving this problem i override GlobalMethodSecurityConfiguration like this

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true)
public class AppMethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {
    @Override
    protected AccessDecisionManager accessDecisionManager() {
        List<AccessDecisionVoter<? extends Object>> decisionVoters = new ArrayList<>();
        ExpressionBasedPreInvocationAdvice expressionAdvice = new ExpressionBasedPreInvocationAdvice();
        expressionAdvice.setExpressionHandler(getExpressionHandler());
        decisionVoters.add(getRoleVoter());
        decisionVoters.add(new AuthenticatedVoter());
        return new AffirmativeBased(decisionVoters);
    }

    private RoleVoter getRoleVoter() {
        RoleVoter e = new RoleVoter();
        e.setRolePrefix("");
        return e;
    }
}


回答6:

You need to use @secured(ROLE_ADMIN) instead of @secured(ADMIN). You are required to write "ROLE_" infront of your role name. Please find the example mentioned below which is making sure only a user with Admin role can access list() method.

@RestController
@RequestMapping("/api/groups")
public class GroupController {

    @Autowired
    private GroupService groupService;

    @Secured("ROLE_ADMIN")
    @RequestMapping
    public List<Group> list() {
        return groupService.findAll();
    }

}


回答7:

Maybe you should register your AppSecurityConfiguration to same context as WebMvcConfig (that extends WebMvcConfigurerAdapter).

AnnotationConfigWebApplicationContext mvcContext = new AnnotationConfigWebApplicationContext();      
mvcContext.register(WebMvcConfig.class, SecurityConfig.class);