I've followed quite a few threads to implement Spring Security to my rest API. Initially I get stuck at #Secured annotation being ignored, now that I got that resolved, I am stuck at getting access denied.
Feels like my problem sound very similar to: #secured with granted authorities throws access denied exception - but I am still getting access denied.
Here's my setup:
spring-security.xml
<authentication-manager>
<authentication-provider user-service-ref="userDetailsService">
<password-encoder ref="passwordEncoder" />
</authentication-provider>
</authentication-manager>
<beans:bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.PlaintextPasswordEncoder"/>
<user-service id="userDetailsService">
<user name="john" password="john1" authorities="ROLE_USER, ROLE_ADMIN" />
<user name="jane" password="jane1" authorities="ROLE_USER" />
<user name="apiuser" password="apiuser" authorities="PERMISSION_TEST" />
</user-service>
Controller:
#Controller
#RequestMapping("/secure")
public class SecureController
{
private static final Logger logger = Logger.getLogger(SecureController.class);
#Secured("PERMISSION_TEST")
#RequestMapping(value = "/makeRequest", method = RequestMethod.GET)
#ResponseBody
public SimpleDTO executeSecureCall()
{
logger.debug("[executeSecureCall] Received request to a secure method");
SimpleDTO dto = new SimpleDTO();
dto.setStringVariable("You are authorized!");
return dto;
}
}
Now - without the proper
<security:global-method-security secured-annotations="enabled"/>
My request goes through (this is because the #Secured annotation is ignored). When I put it in and accessing it using "apiuser"/"apiuser", I kept getting access denied, the debug log:
11:42:43,899 [http-apr-8080-exec-4] DEBUG MethodSecurityInterceptor - Previously Authenticated: org.springframework.security.authentication.UsernamePasswordAuthenticationToken#cc12af5d: Principal: org.springframework.security.core.userdetails.User#d059c8e5: Username: apiuser; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: PERMISSION_TEST; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: PERMISSION_TEST
11:42:43,899 [http-apr-8080-exec-4] DEBUG AffirmativeBased - Voter: org.springframework.security.access.vote.RoleVoter#2a9a42ef, returned: 0
11:42:43,900 [http-apr-8080-exec-4] DEBUG AffirmativeBased - Voter: org.springframework.security.access.vote.AuthenticatedVoter#75a06ec2, returned: 0
11:42:43,902 [http-apr-8080-exec-4] DEBUG AnnotationMethodHandlerExceptionResolver - Resolving exception from handler [com.test.webapp.spring.controller.SecureController#342d150f]: org.springframework.security.access.AccessDeniedException: Access is denied
11:42:43,905 [http-apr-8080-exec-4] DEBUG ResponseStatusExceptionResolver - Resolving exception from handler [com.test.webapp.spring.controller.SecureController#342d150f]: org.springframework.security.access.AccessDeniedException: Access is denied
11:42:43,906 [http-apr-8080-exec-4] DEBUG DefaultHandlerExceptionResolver - Resolving exception from handler [com.test.webapp.spring.controller.SecureController#342d150f]: org.springframework.security.access.AccessDeniedException: Access is denied
11:42:43,909 [http-apr-8080-exec-4] DEBUG DispatcherServlet - Could not complete request
org.springframework.security.access.AccessDeniedException: Access is denied
Thoughts?
Thanks in advance!
As I remember #Secured annotation works only with role names starting ROLE_ by default.
You may switch to #PreAuthorize("hasAuthority('PERMISSION_TEST')") (with pre-post-annotations="enabled") or rename your role.
I want to add a little more to Michail Nikolaev answer.
My answer is from the source code point of view. I want you to understand why access was denied.
From documentation:
When you use a namespace configuration, a default instance of AccessDecisionManager is automatically registered for you and will be used for making access decisions for method invocations and web URL access, based on the access attributes you specify in your intercept-url and protect-pointcut declarations (and in annotations if you are using annotation secured methods). The default strategy is to use an AffirmativeBased AccessDecisionManager with a RoleVoter and an AuthenticatedVoter.
RoleVoter uses ROLE_ prefix (by default) in order to decide if it can vote. You can change that default prefix with RoleVoter.setRolePrefix() method.
From source code:
public class RoleVoter implements AccessDecisionVoter<Object> {
(...)
private String rolePrefix = "ROLE_";
(...)
public void setRolePrefix(String rolePrefix) {
this.rolePrefix = rolePrefix;
}
(...)
public boolean supports(ConfigAttribute attribute) {
if ((attribute.getAttribute() != null) &&
attribute.getAttribute().startsWith(getRolePrefix())) {
return true;
} else {
return false;
}
}
(...)
public int vote(Authentication authentication, Object object,
Collection<ConfigAttribute> attributes) {
int result = ACCESS_ABSTAIN;
Collection<? extends GrantedAuthority> authorities =
extractAuthorities(authentication);
for (ConfigAttribute attribute : attributes) {
if (this.supports(attribute)) {
result = ACCESS_DENIED;
// Attempt to find a matching granted authority
for (GrantedAuthority authority : authorities) {
if (attribute.getAttribute().equals(authority.getAuthority())) {
return ACCESS_GRANTED;
}
}
}
}
return result;
}
PERMISSION_TEST doesn't start with ROLE_ so RoleVoter abstains from deciding. AuthenticatedVoter abstains too (as you have not used IS_AUTHENTICATED_ prefix in #Secured annotation).
Finally, AffirmativeBased implementation of AccessDecisionManager throws AccessDeniedException because both AccessDecisionVoters abstained from voting.
Java docs for AffirmativeBased:
Simple concrete implementation of
org.springframework.security.access.AccessDecisionManager that grants
access if any AccessDecisionVoter returns an affirmative response.
Related
I have following problem with 403 error handling. This is my configuration:
Part of my spring-security.xml
<sec:http auto-config="true" use-expressions="true" entry-point-ref="ep403" disable-url-rewriting="true">
<sec:form-login login-page="/login"
login-processing-url="/login"
username-parameter="email"
password-parameter="password"
/>
<sec:access-denied-handler error-page="/errors/access-denied"/>
<sec:logout invalidate-session="true" delete-cookies="JSESSIONID,SPRING_SECURITY_REMEMBER_ME_COOKIE"
logout-success-url="/login"/>
<sec:session-management session-fixation-protection="newSession"/>
</sec:http>
<bean id="ep403" class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint"/>
If logged/unauthorized user enter to link without rights to view url - he will be redirected to 403 error page and logged out from system if already logged in. I need solution which allow me to redirect logged user to 403 page & logout, but for unauthorized user i want to see login page instead of 403 error, do You know there's any chance to achieve that goal?
I try with my own AuthenticationEntryPoint implementation, I want to check user from Security Context and if its not logged in - execute different action than redirect to forbidden page, but authentication is always null in this case, because logged user on this error page is unfortunately already logged out
public class AccessDeniedEntryPoint implements AuthenticationEntryPoint {
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException arg2) throws IOException, ServletException {
Authenticationauthentication = SecurityContextHolder.getContext().getAuthentication();
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access Denied");
}
}
On your /errors/access-denied mapping check for JSESSIONID,SPRING_SECURITY_REMEMBER_ME_COOKIE cookies. if they are null that means the user had not a successful login to have the cookies, so redirect him to login, if not let him see the access-denied page.
#RequestMapping(value="/errors/access-denied")
public String error(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("JSESSIONID") ||
cookie.getName().equals("SPRING_SECURITY_REMEMBER_ME_COOKIE")) {
return "access-denied";
}
}
return "login";
}
in your security config file I think you might need to add the url of you application that doesn't need or require Authentication for example .
#Override
protected void configure(final HttpSecurity http) throws Exception {
// #formatter:off
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/login*",""
+ "/signin/**",""
+ "/signup/**",""
+ "/api/v1/products/**",""
+ "/api/v1/addproduct/**",""
+ "/api/v1/addCategory/**"
).permitAll()
.anyRequest().authenticated()
// .and()
// .formLogin().loginPage("/login").permitAll()
.and()
.logout();
} // #formatter:on
I'm trying to secure my REST Api with authorization grant flow with spring.
I could obtain (with Postman) an access token, i put the authorization into the header with Bearer, but i could not access resources because Spring Security tell me that:
2017-04-06 17:36:33 [http-nio-8080-exec-9] DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository[186] - HttpSession returned null object for SPRING_SECURITY_CONTEXT
2017-04-06 17:36:33 [http-nio-8080-exec-9] DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository[116] - No SecurityContext was available from the HttpSession: org.apache.catalina.session.StandardSessionFacade#6e24700e. A new one will be created.
2017-04-06 17:36:33 [http-nio-8080-exec-9] DEBUG o.s.s.w.h.writers.HstsHeaderWriter[130] - Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher#3e385c64
2017-04-06 17:36:33 [http-nio-8080-exec-9] DEBUG o.s.security.web.FilterChainProxy[325] - /api/user at position 11 of 14 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
2017-04-06 17:36:33 [http-nio-8080-exec-9] DEBUG o.s.s.w.a.AnonymousAuthenticationFilter[100] - Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken#9057bc48: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#2cd90: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: B1FF11055AA4F347AB8AA7B6E467D93F; Granted Authorities: ROLE_ANONYMOUS'
2017-04-06 17:36:33 [http-nio-8080-exec-9] DEBUG o.s.s.w.a.i.FilterSecurityInterceptor[219] - Secure object: FilterInvocation: URL: /api/user; Attributes: [authenticated]
2017-04-06 17:36:33 [http-nio-8080-exec-9] DEBUG o.s.s.w.a.i.FilterSecurityInterceptor[348] - Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken#9057bc48: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#2cd90: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: B1FF11055AA4F347AB8AA7B6E467D93F; Granted Authorities: ROLE_ANONYMOUS
2017-04-06 17:36:33 [http-nio-8080-exec-9] DEBUG o.s.s.access.vote.AffirmativeBased[66] - Voter: org.springframework.security.web.access.expression.WebExpressionVoter#53b3549c, returned: -1
2017-04-06 17:36:33 [http-nio-8080-exec-9] DEBUG o.s.s.w.a.ExceptionTranslationFilter[173] - Access is denied (user is anonymous); redirecting to authentication entry point
org.springframework.security.access.AccessDeniedException: Access is denied
So basically, after having obtained the access token, if i use it, i will be an Anonymous User, basically because SPRING_SECURITY_CONTEXT is null...
This is my ResourceServer configuration
#EnableResourceServer
#EnableGlobalMethodSecurity(prePostEnabled=true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter{
private final Logger logger = LoggerFactory.getLogger(ResourceServerConfig.class);
#Autowired
DataSource dataSource;
#Override
public void configure(HttpSecurity http) throws Exception {
logger.debug("Api security configured");
http
.antMatcher("/api/**")
.authorizeRequests()
.anyRequest().access("hasRole('USER')")
.and().exceptionHandling().authenticationEntryPoint(new Http403ForbiddenEntryPoint())
.and().httpBasic();
}
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(tokenStore());
}
}
This one is the authentication Server
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
DataSource dataSource;
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authManager;
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore()).authenticationManager(authManager);
}
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
}
I'm trying to access /api/user with this auth Bearer 77a226bf-74a4-4a89-b2a6-e130c215566b which came from the auth server token request after logging in with the user...
What's wrong?
I've had exactly the same issue after updating spring boot from 1.4 to 1.5. The problem was solved by disabling boot's autoconfiguration black magic.
#EnableAutoConfiguration(exclude = {OAuth2AutoConfiguration.class})
I believe they've added some new ~~bug~~ feature which breaks old apps config.
I think User Role is not getting fetched from the database.
Have to define the role column?
This will help you:
https://dzone.com/articles/spring-security-4-authenticate-and-authorize-users
I have this code in my Web Security Config:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/**")
.hasRole("ADMIN")
.and()
.httpBasic().and().csrf().disable();
}
So I added an user with "ADMIN" role in my database and I always get 403 error when I tryed loggin with this user, then I enabled log for spring and I found this line:
2015-10-18 23:13:24.112 DEBUG 4899 --- [nio-8080-exec-1] o.s.s.w.a.i.FilterSecurityInterceptor : Secure object: FilterInvocation: URL: /api/user/login; Attributes: [hasRole('ROLE_ADMIN')]
Why Spring Security is looking for "ROLE_ADMIN" instead "ADMIN"?
Spring security adds the prefix "ROLE_" by default.
If you want this removed or changed, take a look at
How to change role from interceptor-url?
EDIT: found this as well:
Spring Security remove RoleVoter prefix
In Spring 4, there are two methods hasAuthority() and hasAnyAuthority() defined in org.springframework.security.access.expression.SecurityExpressionRoot class. These two methods checks only your custom role name without adding ROLE_ prefix. Definition as follows:
public final boolean hasAuthority(String authority) {
return hasAnyAuthority(authority);
}
public final boolean hasAnyAuthority(String... authorities) {
return hasAnyAuthorityName(null, authorities);
}
private boolean hasAnyAuthorityName(String prefix, String... roles) {
Set<String> roleSet = getAuthoritySet();
for (String role : roles) {
String defaultedRole = getRoleWithDefaultPrefix(prefix, role);
if (roleSet.contains(defaultedRole)) {
return true;
}
}
return false;
}
private static String getRoleWithDefaultPrefix(String defaultRolePrefix, String role) {
if (role == null) {
return role;
}
if (defaultRolePrefix == null || defaultRolePrefix.length() == 0) {
return role;
}
if (role.startsWith(defaultRolePrefix)) {
return role;
}
return defaultRolePrefix + role;
}
Example usage:
<http auto-config="false" use-expressions="true" pattern="/user/**"
entry-point-ref="loginUrlAuthenticationEntryPoint">
<!--If we use hasAnyAuthority, we can remove ROLE_ prefix-->
<intercept-url pattern="/user/home/yoneticiler" access="hasAnyAuthority('FULL_ADMIN','ADMIN')"/>
<intercept-url pattern="/user/home/addUser" access="hasAnyAuthority('FULL_ADMIN','ADMIN')"/>
<intercept-url pattern="/user/home/addUserGroup" access="hasAuthority('FULL_ADMIN')"/>
<intercept-url pattern="/user/home/deleteUserGroup" access="hasAuthority('FULL_ADMIN')"/>
<intercept-url pattern="/user/home/**" access="hasAnyAuthority('FULL_ADMIN','ADMIN','EDITOR','NORMAL')"/>
<access-denied-handler error-page="/403"/>
<custom-filter position="FORM_LOGIN_FILTER" ref="customUsernamePasswordAuthenticationFilter"/>
<logout logout-url="/user/logout"
invalidate-session="true"
logout-success-url="/user/index?logout"/>
<!-- enable csrf protection -->
<csrf/>
</http> <beans:bean id="loginUrlAuthenticationEntryPoint"
class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<beans:constructor-arg value="/user"/>
</beans:bean>
As #olyanren said, you can use hasAuthority() method in Spring 4 instead of hasRole(). I am adding JavaConfig example:
#Override
protected void configure(HttpSecurity http) throws Exception {
.authorizeRequests()
.antMatchers("/api/**")
.access("hasAuthority('ADMIN')")
.and()
.httpBasic().and().csrf().disable();
}
You can create a mapper to add ROLE_ at the beginning of all of your roles:
#Bean
public GrantedAuthoritiesMapper authoritiesMapper() {
SimpleAuthorityMapper mapper = new SimpleAuthorityMapper();
mapper.setPrefix("ROLE_"); // this line is not required
mapper.setConvertToUpperCase(true); // convert your roles to uppercase
mapper.setDefaultAuthority("USER"); // set a default role
return mapper;
}
The you should add the mapper to your provider:
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
// your config ...
provider.setAuthoritiesMapper(authoritiesMapper());
return provider;
}
_ROLE prefix is used by spring security, to identify that it is as a role. A role has a set of privileges a.k.a Authorities, these authorities define varies permissions for a role.
ex:- EDIT_PROFILE, DELETE_PROFILE
You can define both the roles and authorities, if you are defining a role then it must be prefixed with "ROLE_"
In your case you are looking for a role, so by default spring security looks for a string that is prefixed with "ROLE_".
well i try to redirect user base on role but my custom class do nothing i do some system out but nothing happen.
i follow this small tutorial http://oajamfibia.wordpress.com/2011/07/07/role-based-login-redirect/#comment-12
but i change my extention class for SavedRequestAwareAuthenticationSuccessHandler i also try the one in the tutorial nothing happen don't know what i missing. Any help will be appreciate.
this is my class
#Component
public class RoleBaseAuthentification extends SavedRequestAwareAuthenticationSuccessHandler{
private Map<String, String> roleMap;
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws ServletException, IOException {
System.out.println(request.getUserPrincipal());
if(authentication.getPrincipal() instanceof UserDetails){
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
System.out.println(userDetails);
String role = userDetails.getAuthorities().isEmpty() ? null : userDetails.getAuthorities().toArray()[0].toString();
System.out.println(role);
response.sendRedirect(request.getContextPath() + roleMap.get(role));
}
super.onAuthenticationSuccess(request, response, authentication);
}
public Map<String, String> getRoleMap() {
return roleMap;
}
public void setRoleMap(Map<String, String> roleMap) {
this.roleMap = roleMap;
}
}
and here is my security-context.xml
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider>
<security:jdbc-user-service data-source-ref="dataSource"
users-by-username-query="select username,password,enabled from users where username = ?"
authorities-by-username-query="select u.username, ur.authority from users u, user_roles ur where u.user_id = ur.user_id and u.username = ?" />
</security:authentication-provider>
</security:authentication-manager>
<security:http use-expressions="true">
<security:intercept-url pattern="/management" access="hasRole('ROLE_ADMIN')"/>
<security:form-login login-page="/login" authentication-success-handler-ref="redirectByRole"/>
</security:http>
<bean id="redirectByRole" class="com.freelogic.spring.web.service.RoleBaseAuthentification">
<property name="roleMap">
<map>
<entry key="ROLE_ADMIN" value="/management.jsp" />
<entry key="ROLE_USER" value="/home.jsp" />
</map>
</property>
</bean>
The problem
Your success handler extends SavedRequestAwareAuthenticationSuccessHandler so calling
super.onAuthenticationSuccess(request, response, authentication)
causes SavedRequestAwareAuthenticationSuccessHandler or one of it's super classes to use their redirect strategies and override yours.
The (ergghh) solution
You can prevent the redirect by calling super.onAuthenticationSuccess() before your reponse.sendRedirect(). It will override the redirecting attempts previously made. While not a good solution it will work. See below why I think it's not a good solution.
On a side note
I'm not sure why you are extending SavedRequestAwareAuthenticationSuccessHandler and not simply implementing AuthenticationSuccessHandler. The former allows a authenticated user to redirect to it's previous visited page. Your RoleBaseAuthentification only redirects by role with no condition to return to a previous url. So your choice to extend SavedRequestAwareAuthenticationSuccessHandler does not make sense to me.
I need to add a custom parameter to authentication object. So I create a wrapper for Authentication object which contains custom parameter. Then create this object in CustomAuthenticationProcessingFilter.
public class CustomAuthenticationProcessingFilter extends UsernamePasswordAuthnticationFilter {
public Authentication attemptAuthentication(...){
Authentication auth = super.attemptAuthentication(...);
CustomAuthentication custAuth = new CustomAuthentication(auth);
custAuth.setCustomParameter("");
return custAuth;
}
}
Then in CustomAuthenticationProvider I get Authentication object and want to cast it to CustomAuthentication. This throws ClassCastException. Isn't the object passed to CustomAuthenticationProvider the same which I created in CustomAuthenticationProcessingFilter?
Here is my Spring Security Config -
<sec:http entry-point-ref="entryPoint" auto-config="false"> ... <sec:custom-filter ref="customAuthenticationProcessingFilter" position="FORM_LOGIN_FILTER"/> </sec:http>