I am trying to use Spring Security to secure my Rest API's.
So my requirement is that user should pass an apiKey in header with api call, and it will get validated w.r.t to predefined credentials.
So let us say i have apikey : 'ABCdEfG' with Role: 'ROLE_ADMIN'
So i wrote cutom implimentation of security filter and authentication provider.
The authentication with respect to apiKey is working fine but it is not validating role required for the perticular api.
i.e. i am not able to access my api without apiKey, but the required role it is not able to validate.
My current Implementation looks as follows:
please let me know if i am doing wrong somewhere.
Application context:
<security:global-method-security
pre-post-annotations="enabled" />
<security:http entry-point-ref="authenticationEntryPoint"
create-session="stateless">
<security:intercept-url pattern="/api/*"
access="ROLE_ADMIN" />
<security:custom-filter before="FORM_LOGIN_FILTER"
ref="restAuthenticationFilter" />
</security:http>
<bean id="restAuthenticationFilter"
class="com.myapp.authentication.RestAuthenticationFilter2">
<property name="authenticationManager" ref="authenticationManager" />
<property name="authenticationSuccessHandler" ref="authenticationSuccessHandler" />
</bean>
<bean class="com.myapp.authentication.RestAuthenticationEntryPoint"
id="authenticationEntryPoint"></bean>
<bean
class="com.myapp.authentication.RestAuthenticationSuccessHandler"
id="authenticationSuccessHandler"></bean>
<bean class="com.myapp.authentication.CustomAuthenticationProvider"
id="customAuthenticationProvider"></bean>
<bean class="com.myapp.authentication.util.UserAuthenticationDAO"
factory-method="getInstance" id="userAuthenticationDAO"></bean>
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider
ref="customAuthenticationProvider" />
</security:authentication-manager>
Role.java
import org.springframework.security.core.GrantedAuthority;
#SuppressWarnings("serial")
public class Role implements GrantedAuthority {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthority() {
return this.name;
}
}
User.java
import java.util.List;
import org.springframework.security.core.userdetails.UserDetails;
#SuppressWarnings("serial")
public class User implements UserDetails {
private String apiKey;
/* Spring Security related fields */
private List<Role> authorities;
private boolean accountNonExpired = true;
private boolean accountNonLocked = true;
private boolean credentialsNonExpired = true;
private boolean enabled = true;
public String getApiKey() {
return apiKey;
}
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
public List<Role> getAuthorities() {
return authorities;
}
public void setAuthorities(List<Role> authorities) {
this.authorities = authorities;
}
public boolean isAccountNonExpired() {
return accountNonExpired;
}
public void setAccountNonExpired(boolean accountNonExpired) {
this.accountNonExpired = accountNonExpired;
}
public boolean isAccountNonLocked() {
return accountNonLocked;
}
public void setAccountNonLocked(boolean accountNonLocked) {
this.accountNonLocked = accountNonLocked;
}
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
public void setCredentialsNonExpired(boolean credentialsNonExpired) {
this.credentialsNonExpired = credentialsNonExpired;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
#Override
public String getPassword() {
// TODO Auto-generated method stub
return null;
}
#Override
public String getUsername() {
// TODO Auto-generated method stub
return null;
}
#Override
public boolean equals(Object obj) {
return this.apiKey.equals(((User) obj).getApiKey());
}
}
CustomAuthentiCationToken.java
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
public class CustomAuthenticationToken extends UsernamePasswordAuthenticationToken {
/**
*
*/
private static final long serialVersionUID = 1L;
private String token;
public CustomAuthenticationToken(String token) {
super(null, null);
this.token = token;
}
public String getToken() {
return token;
}
#Override
public Object getCredentials() {
return null;
}
#Override
public Object getPrincipal() {
return null;
} }
AuthenticationFilter
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import com.myapp.authentication.bean.CustomAuthenticationToken;
public class RestAuthenticationFilter2 extends AbstractAuthenticationProcessingFilter {
protected RestAuthenticationFilter2() {
super("/**");
}
#Override
protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
return true;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
String header = request.getHeader("Authorization");
if (header == null) {
throw new BadCredentialsException("No token found in request headers");
}
//String authToken = header.substring(7);
String authToken = header.trim();
CustomAuthenticationToken authRequest = new CustomAuthenticationToken(authToken);
return getAuthenticationManager().authenticate(authRequest);
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, Authentication authResult) throws IOException, ServletException {
super.successfulAuthentication(request, response, chain, authResult);
// As this authentication is in HTTP header, after success we need to
// continue the request normally
// and return the response as if the resource was not secured at all
chain.doFilter(request, response);
}
}
AuthenticationProvider
public class CustomAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
#Autowired
RetinaAuthenticationService retinaAuthenticationService;
#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 {
CustomAuthenticationToken customAuthenticationToken = (CustomAuthenticationToken) authentication;
String token = customAuthenticationToken.getToken();
User user = retinaAuthenticationService.loadUserByApiKey(token);
if (null != user) {
return user;
} else {
throw new BadCredentialsException("API token is not valid");
}
}
}
According to the security configuration you wrote
<security:http entry-point-ref="authenticationEntryPoint"
create-session="stateless">
<security:intercept-url pattern="/api/*"
access="ROLE_ADMIN" />
<security:custom-filter before="FORM_LOGIN_FILTER"
ref="restAuthenticationFilter" />
</security:http>
You are stating that any incoming request to /api/* (what means that http://localhost:8080/myapp/api/test will be secured, but neither http://localhost:8080/myapp/api/ nor http://localhost:8080/myapp/api/more/test, these are not secured) must have ROLE_ADMIN as granted authority.
As you set create-session as stateless, any request must be validated, so you must include authentication credentials (in this case, the APIKEY) in every request.
Once the APIKEY is validated (so the request gets authenticated), then it would be checked if the Authentication instance returned by your CustomAuthenticationProvider has ROLE_ADMIN as granted autority. But you don't have to check it by your own, the spring-security filter chain (org.springframework.web.filter.DelegatingFilterProxy) would do it by itself.
So, there is no need to access by yourself to the authority you configured in access attribute of security:intercept-url element.
That finally means that if the User object returned by the provider has ROLE_ADMIN as authority in the List authorities, it will be allowed to hit the endpoint /api/test, otherwise not.
EDIT: I was quite annoyed, so I have tested your configuration, by copying the classes you posted and building the other stuff.
I build a fixed implementation of RetinaAuthenticationService like this, as was the piece left, based on an interface with method loadUserByApikey():
public interface RetinaAuthenticationService {
public abstract User loadUserByApiKey(String token);
}
The implementation:
public class RetinaAuthenticationServiceImpl implements RetinaAuthenticationService {
private Map<String, List<String>> apiKeyRoleMappings;
#Override
public User loadUserByApiKey(String token) {
User user = null;
if(this.apiKeyRoleMappings.containsKey(token)){
user = new User();
user.setApiKey(token);
List<Role> authorities = new ArrayList<Role>();
for(String roleStr : this.apiKeyRoleMappings.get(token)){
Role role = new Role();
role.setName(roleStr);
authorities.add(role);
}
user.setAuthorities(authorities );
user.setAccountNonExpired(true);
user.setAccountNonLocked(true);
user.setCredentialsNonExpired(true);
user.setEnabled(true);
}else{
throw new BadCredentialsException("ApiKey " + token + " not found");
}
return user;
}
public Map<String, List<String>> getApiKeyRoleMappings() {
return apiKeyRoleMappings;
}
public void setApiKeyRoleMappings(Map<String, List<String>> apiKeyRoleMappings) {
this.apiKeyRoleMappings = apiKeyRoleMappings;
}
}
Then I configured all in securiy-context.xml in a running project for testing purposes:
<security:http auto-config='false' pattern="/api/**" entry-point-ref="serviceAccessDeniedHandler" create-session="stateless" use-expressions="false">
<security:intercept-url pattern="/api/*" access="ROLE_ADMIN" />
<security:intercept-url pattern="/api/user/*" access="ROLE_USER,ROLE_ADMIN" />
<security:custom-filter before="FORM_LOGIN_FILTER" ref="restAuthenticationFilter" />
<security:csrf disabled="true"/>
</security:http>
<beans:bean id="restAuthenticationFilter"
class="com.eej.test.security.filter.RestAuthenticationFilter2">
<beans:property name="authenticationManager" ref="apiAuthenticationManager" />
<beans:property name="authenticationSuccessHandler" ref="authenticationSuccessHandler" />
</beans:bean>
<beans:bean id="retinaAuthenticationServiceImpl" class="com.eej.test.security.services.RetinaAuthenticationServiceImpl">
<beans:property name="apiKeyRoleMappings">
<beans:map>
<beans:entry key="aaaaa">
<beans:list>
<beans:value>ROLE_USER</beans:value>
</beans:list>
</beans:entry>
<beans:entry key="bbbbb">
<beans:list>
<beans:value>ROLE_ADMIN</beans:value>
</beans:list>
</beans:entry>
<beans:entry key="ccccc">
<beans:list>
<beans:value>ROLE_USER</beans:value>
<beans:value>ROLE_ADMIN</beans:value>
</beans:list>
</beans:entry>
</beans:map>
</beans:property>
</beans:bean>
<!-- bean class="com.myapp.authentication.RestAuthenticationEntryPoint" id="authenticationEntryPoint"></bean-->
<beans:bean
class="com.eej.test.security.handler.RestAuthenticationSuccessHandler" id="authenticationSuccessHandler" />
<beans:bean class="com.eej.test.security.CustomAuthenticationProvider" id="customAuthenticationProvider" />
<!-- beans:bean class="com.myapp.authentication.util.UserAuthenticationDAO" factory-method="getInstance" id="userAuthenticationDAO" /-->
<security:authentication-manager alias="apiAuthenticationManager">
<security:authentication-provider ref="customAuthenticationProvider" />
</security:authentication-manager>
I made minor changes to yours (use a pre-existing entry point ref, apply a pattern to security:http section, as I already have an universal one in this project, set use-expressions to false, disable auto-config and disable csrf), changed the package name and comment unnecessary elements
I had to configure a bean for my class RetinaAuthenticationServiceImpl ,
where I set a map with this apikey-role mappings:
aaaaa > ROLE_USER
bbbbb > ROLE_ADMIN
ccccc > ROLE_USER,ROLE_ADMIN
And all works as it should. An access to a http://host:port/context/api/test return 200 where using token bbbbb and ccccc and 403 while using aaaaa.
Related
I made a custom filter and failureHandler. But to make it work i need to register handler in filter. So i willd be glad if someone will write how to do it in my code. I know that there are a lot of examples in stackowerflow but i'm new to spring and java and to understand how it works i need an example for my app. Please don't answer with "this is a duplicate".
My filter:
#Component("MyAuthFilter")
public class MyAuthFilter extends UsernamePasswordAuthenticationFilter {
private int errCode = 5;
#Autowired
#Qualifier("authenticationManager")
#Override
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
super.setAuthenticationManager(authenticationManager);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
System.out.println("running my own version of UsernmePasswordFilter ... ");
String login = (String) request.getParameter("login");
String password = (String) request.getParameter("password");
errCode = validate(login, password);
System.out.println(login + " - " + password);
System.out.println(request.getQueryString());
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(login, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
private int validate(String login, String password) {
if (login.isEmpty() && password.isEmpty()) {
return 4;
}
if (login.isEmpty() && !password.isEmpty()) {
return 2;
}
if (!login.isEmpty() && password.isEmpty()) {
return 3;
}
return 1;
}
}
here is my handler:
public class LoginFailureHandler extends SimpleUrlAuthenticationFailureHandler {
public LoginFailureHandler() {
System.out.println("i debug");
}
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
System.out.println("do smth");
super.onAuthenticationFailure(request, response, exception);
}
}
and my spring-security.xml:
<beans:bean id="authenticationFailureHandler" class="com.webproject.LoginFailureHandler" />
<http auto-config="true" use-expressions="true">
<intercept-url pattern="/courses*" access="hasRole('ROLE_USER')" />
<custom-filter before="FORM_LOGIN_FILTER" ref="MyAuthFilter" />
<form-login
login-page="/login"
default-target-url="/courses"
username-parameter="loginField"
password-parameter="passwordField"
authentication-failure-handler-ref="authenticationFailureHandler"
/>
<csrf disabled="true" />
</http>
<authentication-manager alias="authenticationManager">
<authentication-provider>
<user-service>
<user name="ars" password="1234" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
</beans:beans>
Declare to beans and autowire them to your filter
#Bean
public AuthenticationFailureHandler getFailureHandler(){
SimpleUrlAuthenticationFailureHandler handler = new SimpleUrlAuthenticationFailureHandler();
handler.setDefaultFailureUrl("/login.html");
return handler;
}
MyAuthFilter
#Autowired
#Qualifier("authenticationManager")
#Override
public void setAuthenticationManager(AuthenticationManager authenticationManager, AuthenticationFailureHandler failureHandler) {
this.setAuthenticationManager(authenticationManager);
this.setAuthenticationFailureHandler(failureHandler);
}
In my Spring project, I have defined my own custom authentication provider. Before bringing in Spring Security, I used BCrypt in Java code and now passwords are saved after BCrypting in Database.
spring-security.xml
<security:authentication-manager>
<security:authentication-provider ref="myAuthenticationProvider">
</security:authentication-provider>
</security:authentication-manager>
<b:bean id="bcryptEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />
<b:bean id="myAuthenticationProvider" class="com.cT.www.provider.CustomAuthenticationProvider">
</b:bean>
And my custom authentication provider looks as follows.
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
public CustomAuthenticationProvider() {
super();
}
#Autowired
private PersonService personService;
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
System.out.println(authentication.getName() + "principal" +(String) authentication.getCredentials() );
String username = authentication.getName();
String password = (String) authentication.getCredentials();
UserSignUp user = (UserSignUp) personService.loadUserByUsername(username);
if (user == null || !user.getUsername().equalsIgnoreCase(username)) {
throw new BadCredentialsException("Username not found.");
}
if (!password.equals(user.getPassword())) {
throw new BadCredentialsException("Wrong password.");
}
List<Role> authorities = user.getAuthorities();
return new UsernamePasswordAuthenticationToken(user, password, authorities);
}
#Override
public boolean supports(Class<?> arg0) {
// TODO Auto-generated method stub
return true;
}
}
I don't wanna use user-service-ref in spring-security.xml wihtin authentication-manager.
If your user passwords are already saved as BCrypt in database you don't need much of thing to do. In your authenticate method just replace your password checking condition with below
if (BCrypt.checkpw(password, user.getPassword())) {
throw new BadCredentialsException("Wrong password.");
}
Refer BCrypt source for more details.
You can refer to BCryptPasswordEncoder this way:
<authentication-manager>
<authentication-provider>
<password-encoder ref="encoder" />
</authentication-provider>
</authentication-manager>
<beans:bean id="encoder"
class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder">
<beans:constructor-arg name="strength" value="11" />
</beans:bean>
For details see http://www.mkyong.com/spring-security/spring-security-password-hashing-example/
I'm working on a new REST Server that I develop with spring. The Server must have only server side logic, nothing js or views.
At the moment I started with Spring-boot version 1.2.7, but this only to give a server to the front-end developers until the setup for the server is done.
After I will change to the spring-core version 4.1 or something similar.
At the moment I have difficulty in configure the security part.
At the beginning I started with a Java Config configuration but after I changed to xml, because I already had a similar configuration.
My end result must be these:
hostname **/api/auth**** :is the entry point where a frontend developer make a **POST request with username and password of a customer. This call returns a token. This token permits me to identify the user the next time.
hostname /api/secure/resource/1 : is a resource that is protected, and can be accessible only with a valid token
hostname /api/other/1 : is an other type of resource that is not protected and can be accessible for everyone
hostname /api/secure/bambi/: is a resource that can be accessed from everyone but if it has a token then, more object-parameters are shown.
This seams for me a relative simple configuration, but I'm not able to configure it.
I know that this is not very his work, but for handle the token, and the resource access I would use OAUTH2 infrastructure (I'know, that this can be done better, but this was a requisite)
As follow I write you my configuration:
StartUpApplication.java
#SpringBootApplication(exclude = DispatcherServletAutoConfiguration.class)
#Import({ InMemoryDBConfigurationImpl.class})
#ImportResource({ "classpath:config/security-context.xml" })
public class SalustroApplication {
#Autowired
#Qualifier("InMemoryConfig")
private SystemConfiguration systemConfiguration;
public static void main(String[] args) {
SpringApplication app = new SpringApplication(SalustroApplication.class);
app.run(args);
}
#Bean
public ServletRegistrationBean foo() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
applicationContext.register(FooConfig.class);
dispatcherServlet.setApplicationContext(applicationContext);
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(dispatcherServlet, "/");
servletRegistrationBean.setName("foo");
return servletRegistrationBean;
}
Do I need the foo method for the security part?
security-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:oauth="http://www.springframework.org/schema/security/oauth2"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:sec="http://www.springframework.org/schema/security"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<sec:http pattern="/api/auth" create-session="stateless"
authentication-manager-ref="clientAuthenticationManager" use-expressions="true">
<sec:intercept-url pattern="/api/auth" access="IS_AUTHENTICATED_FULLY" />
<sec:anonymous enabled="false" />
<sec:custom-filter ref="clientCredentialsTokenEndpointFilter"
after="BASIC_AUTH_FILTER" />
<sec:access-denied-handler ref="oauthAccessDeniedHandler" />
<sec:http-basic entry-point-ref="clientAuthenticationEntryPoint" />
<sec:csrf disabled="true" />
</sec:http>
<sec:http pattern="/api/**" create-session="never"
entry-point-ref="oauthAuthenticationEntryPoint" use-expressions="true"
access-decision-manager-ref="accessDecisionManager">
<sec:intercept-url pattern="/api/**"
access="isFullyAuthenticated() AND hasRole('ROLE_USER')" />
<sec:anonymous enabled="false" />
<sec:custom-filter ref="resourceServerFilter"
before="PRE_AUTH_FILTER" />
<sec:access-denied-handler ref="oauthAccessDeniedHandler" />
<sec:csrf disabled="true" />
<sec:headers />
</sec:http>
<sec:authentication-manager id="clientAuthenticationManager">
<sec:authentication-provider
user-service-ref="clientDetailsUserService" />
</sec:authentication-manager>
<bean id="accessDecisionManager"
class="org.springframework.security.access.vote.UnanimousBased"
c:decisionVoters-ref="votersList" />
<bean id="clientAuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint"
p:realmName="p4me-test/client" p:typeName="Basic" />
<bean id="clientCredentialsTokenEndpointFilter"
class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter"
p:authenticationManager-ref="clientAuthenticationManager"
p:filterProcessesUrl="/api/auth" />
<bean id="clientDetailsService"
class="app.security.ClientDetailsServiceImpl" />
<bean id="clientDetailsUserService"
class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService"
c:clientDetailsService-ref="clientDetailsService" />
<bean id="clientDetailServiceImpl" class="app.security.ClientDetailsServiceImpl" />
<bean id="oauthAccessDeniedHandler"
class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler" />
<bean id="oauthAuthenticationEntryPoint"
class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint"
p:realmName="p4me-test">
</bean>
<oauth:resource-server id="resourceServerFilter"
resource-id="test" token-services-ref="tokenServices" />
<bean id="tokenEnhancer" class="app.security.CustomTokenEnhancer" />
<bean id="tokenServices" class="app.security.CustomTokenServices"
p:tokenStore-ref="tokenStore" p:clientDetailsService-ref="clientDetailsService"
p:supportRefreshToken="true" p:tokenEnhancer-ref="tokenEnhancer"
p:accessTokenValiditySeconds="1800" />
<bean id="tokenStore" class="org.springframework.security.oauth2.provider.token.store.InMemory TokenStore" />
<sec:authentication-manager alias="authenticationManager">
<sec:authentication-provider ref="userAuthenticationProvider" />
</sec:authentication-manager>
<bean id="userAuthenticationProvider"
class="app.config.impl.security.SecureAuthenticationProvider"/>
<oauth:authorization-server
client-details-service-ref="clientDetailsService"
token-services-ref="tokenServices" user-approval-handler-ref="userApprovalHandler"
token-endpoint-url="/api/auth">
<oauth:authorization-code />
<oauth:implicit />
<oauth:refresh-token />
<oauth:client-credentials />
<oauth:password />
</oauth:authorization-server>
<bean id="userApprovalHandler"
class="org.springframework.security.oauth2.provider.approval.TokenStoreUserApprovalHandler"
p:tokenStore-ref="tokenStore" p:requestFactory-ref="requestFactory" />
<bean id="requestFactory"
class="org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory"
c:clientDetailsService-ref="clientDetailServiceImpl" />
<util:list id="votersList">
<bean class="app.security.AccessVoter" />
<bean class="org.springframework.security.oauth2.provider.vote.ScopeVoter" />
<bean class="org.springframework.security.access.vote.RoleVoter" />
<bean class="org.springframework.security.access.vote.AuthenticatedVoter" />
<bean class="org.springframework.security.web.access.expression.WebExpressionVoter">
<property name="expressionHandler">
<bean class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler" />
</property>
</bean>
</util:list>
The test class
#WebAppConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = SalustroApplication.class)
public class AuthTest {
#Autowired
private WebApplicationContext context;
#Autowired
private Filter springSecurityFilterChain;
#Test
public void find1() throws Exception {
ResultActions doLogin = doLogin();
String contentAsString = doLogin.andReturn().getResponse().getContentAsString();
JSONObject json = new JSONObject(contentAsString);
DefaultMockMvcBuilder webAppContextSetup = MockMvcBuilders.webAppContextSetup(context)
.addFilter(springSecurityFilterChain);
MockMvc build = webAppContextSetup.build();
final ResultActions userResult = build.perform(post("/api/secure/user/1")
.param("access_token", json.getString("access_token")).accept(MediaType.APPLICATION_JSON))
.andDo(print());
assertEquals(someUser, userResult);
}
protected ResultActions doLogin() throws Exception {
DefaultMockMvcBuilder webAppContextSetup = MockMvcBuilders.webAppContextSetup(context)
.addFilter(springSecurityFilterChain);
MockMvc build = webAppContextSetup.build();
final ResultActions loginResult = build.perform(post("/api/auth").param("grant_type", "password")
.param("client_id", "testId").param("client_secret", "testSecret").param("username", "someUser")
.param("password", "somePassword").param("scope", "read").accept(MediaType.APPLICATION_JSON)).andDo(print());
return loginResult;
}
}
SecureAuthenticationPriver.class
#Component
public class SecureAuthenticationProvider implements AuthenticationProvider {
protected final static Logger logger = LoggerFactory.getLogger(SecureAuthenticationProvider.class);
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String name = authentication.getName();
String password = authentication.getCredentials().toString();
List<GrantedAuthority> grantedAuths = new ArrayList<>();
GrantedAuthority authorithy = new SimpleGrantedAuthority("USER");
grantedAuths.add(authorithy);
UserEntity authenticatedUser = userPersistence.findByUserName(name, password);
if (authenticatedUser != null) {
return new UsernamePasswordAuthenticationToken(name, password, grantedAuths);
} else
return null; }
#Override
public boolean supports(Class<?> authentication) {
return false;
}
}
AccessVoter.class
#Service
public class AccessVoter implements AccessDecisionVoter<Object> {
#Override
public boolean supports(final ConfigAttribute attribute) {
return true;
}
#Override
public boolean supports(final Class<?> clazz) {
return true;
}
#Override
#Transactional
public int vote(final Authentication authentication, final Object object,
final Collection<ConfigAttribute> attributes) {
final Object principal = authentication.getPrincipal();
return 1;
}
private int refreshUserDetails(final Principal principal) {
return 1;
}
}
ClientDetailServiceImpl.class
public class ClientDetailsServiceImpl implements ClientDetailsService {
#Override
public ClientDetails loadClientByClientId(final String clientId) {
if ("invalid".equals(clientId)) {
throw new ClientRegistrationException(clientId + " not found");
}
return createClientDetails(clientId);
}
private ClientDetails createClientDetails(final String clientId) {
final Set<GrantedAuthority> grantAuthorities = new HashSet<GrantedAuthority>();
grantAuthorities.add(new SimpleGrantedAuthority("ROLE_USER"));
final Set<String> authorizedGrantTypes = new HashSet<String>();
authorizedGrantTypes.add("password");
final BaseClientDetails details = new BaseClientDetails();
details.setClientId("testId");
details.setClientSecret("testSecret");
details.setAuthorizedGrantTypes(authorizedGrantTypes);
details.setAuthorities(grantAuthorities);
return details;
}
}
CustomTokenEnhancer.class
#Component
public class CustomTokenEnhancer implements TokenEnhancer {
private List<TokenEnhancer> delegates = Collections.emptyList();
#Autowired
private UserService userService;
public void setTokenEnhancers(final List<TokenEnhancer> delegates) {
this.delegates = delegates;
}
#Override
public OAuth2AccessToken enhance(final OAuth2AccessToken accessToken, final OAuth2Authentication authentication) {
final DefaultOAuth2AccessToken tempResult = (DefaultOAuth2AccessToken) accessToken;
// tempResult.setAdditionalInformation(getAuthenticationMethod(authentication));
OAuth2AccessToken result = tempResult;
for (final TokenEnhancer enhancer : delegates) {
result = enhancer.enhance(result, authentication);
}
return result;
}
private boolean isAdmin(final Collection<GrantedAuthority> authorities) {
for (final GrantedAuthority grantedAuthority : authorities) {
if (grantedAuthority.getAuthority().compareTo("ROLE_ADMIN") == 0) {
return true;
}
}
return false;
}
}
CustomTokenServices.class
public class CustomTokenServices extends DefaultTokenServices {
private TokenStore tokenStore;
private ClientDetailsService clientDetailsService;
private TokenEnhancer accessTokenEnhancer;
#Override
public void afterPropertiesSet() throws Exception {
Assert.notNull(tokenStore, "tokenStore must be set");
}
#Override
public OAuth2AccessToken createAccessToken(final OAuth2Authentication authentication) {
final OAuth2AccessToken existingAccessToken = tokenStore
.getAccessToken(authentication);
OAuth2RefreshToken refreshToken = null;
if (existingAccessToken != null && existingAccessToken.isExpired()) {
if (existingAccessToken.getRefreshToken() != null) {
refreshToken = existingAccessToken.getRefreshToken();
tokenStore.removeRefreshToken(refreshToken);
}
tokenStore.removeAccessToken(existingAccessToken);
}
refreshToken = createRefreshToken(authentication);
final ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;
if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {
refreshToken = createRefreshToken(authentication);
}
final OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
tokenStore.storeAccessToken(accessToken, authentication);
refreshToken = accessToken.getRefreshToken();
if (refreshToken != null) {
tokenStore.storeRefreshToken(refreshToken, authentication);
}
return accessToken;
}
#Override
public OAuth2Authentication loadAuthentication(final String accessTokenValue) {
final DefaultOAuth2AccessToken accessToken = (DefaultOAuth2AccessToken) tokenStore
.readAccessToken(accessTokenValue);
if (accessToken == null) {
throw new InvalidTokenException("Invalid access token: " + accessTokenValue);
} else if (accessToken.isExpired()) {
tokenStore.removeAccessToken(accessToken);
throw new InvalidTokenException("Access token expired: " + accessTokenValue);
}
final OAuth2Authentication result = tokenStore
.readAuthentication(accessToken);
if (clientDetailsService != null) {
final String clientId = result.getOAuth2Request().getClientId();
try {
clientDetailsService.loadClientByClientId(clientId);
} catch (final ClientRegistrationException e) {
throw new InvalidTokenException("Client not valid: " + clientId, e);
}
}
final int validitySeconds = getAccessTokenValiditySeconds(result
.getOAuth2Request());
accessToken
.setExpiration(new Date(System.currentTimeMillis() + validitySeconds * 1000L));
return result;
}
private ExpiringOAuth2RefreshToken createRefreshToken(final OAuth2Authentication authentication) {
if (!isSupportRefreshToken(authentication.getOAuth2Request())) {
return null;
}
final int validitySeconds = getRefreshTokenValiditySeconds(authentication
.getOAuth2Request());
final ExpiringOAuth2RefreshToken refreshToken = new DefaultExpiringOAuth2RefreshToken(UUID
.randomUUID().toString(), new Date(System.currentTimeMillis() + validitySeconds * 1000L));
return refreshToken;
}
private OAuth2AccessToken createAccessToken(final OAuth2Authentication authentication, final OAuth2RefreshToken refreshToken) {
final DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID
.randomUUID().toString());
final int validitySeconds = getAccessTokenValiditySeconds(authentication
.getOAuth2Request());
if (validitySeconds > 0) {
token.setExpiration(new Date(System.currentTimeMillis() + validitySeconds * 1000L));
}
token.setRefreshToken(refreshToken);
token.setScope(authentication.getOAuth2Request().getScope());
return accessTokenEnhancer != null ? accessTokenEnhancer
.enhance(token, authentication) : token;
}
#Override
public void setTokenEnhancer(final TokenEnhancer accessTokenEnhancer) {
this.accessTokenEnhancer = accessTokenEnhancer;
}
#Override
public void setTokenStore(final TokenStore tokenStore) {
this.tokenStore = tokenStore;
}
#Override
public void setClientDetailsService(final ClientDetailsService clientDetailsService) {
this.clientDetailsService = clientDetailsService;
}
}
How I said at the beginning: I started from an other working copy of configuration, and transformed to the needs of this application.
It is also possible that I confused a little bit some config.
I repeat the at the end I would utilize the OAUTH2 system to generate a token and utilize this token to authenticate the users. This authentication is made at with /api/auth (or /api/secure/auth?), the resources are available under /api/secure only for user with a valid token and other resources are available under /api/yyy and if they have a token more info is returned
When I run the test to try to make take a resource I receive this error:
Body = {"error":"access_denied","error_description":"Access is denied"}
Now I don't know exactely where I have to operate. In the security-context.xml or add some class to check the token.
The Exception
Body = {"error":"unauthorized","error_description":"There is no client authentication. Try adding an appropriate authentication filter."}
Is actually related to AuthTest test class where the WebApplicationContext doesn't contain the filter chain for the Spring Security. You need to make the following changes for your AuthTest test class.
#WebAppConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = SalustroApplication.class)
public class AuthTest {
#Autowired
private WebApplicationContext context;
// Inject this
#Autowired
private Filter springSecurityFilterChain;
#Test
public void testLogin() throws Exception {
ResultActions doLogin = doLogin();
assertEquals(doLogin.andReturn().getResponse().getContentAsString(), "A valid Token");
}
protected ResultActions doLogin() throws Exception {
DefaultMockMvcBuilder webAppContextSetup = MockMvcBuilders.webAppContextSetup(context).addFilter(springSecurityFilterChain); // Add filter
MockMvc build = webAppContextSetup.build();
final ResultActions loginResult = build.perform(post("/api/auth").param("grant_type", "password")
.param("client_id", "testId").param("client_secret", "testSecret").param("username", "aUserName")
.param("password", "123456").accept(MediaType.APPLICATION_JSON)).andDo(print());
return loginResult;
}
}
I am working with spring-security 3.1 to implement two different logons. The first thing I have to a database that brings me CustomUserDetailService credentials datos.Este the same database is for administrator access. The second port is for the user but the information comes from a web service I call him with me a method validates the user. The problem I have with the second port, and to develop a CustomAuthenticationProvider for the second AuthenticationManager (web service), but when I try to access the spring-security user sends me to error page login.html? Error = true the furmulario administrator access. Esteb is my configuration file:
<http pattern="../resources/**" security="none" />
<http pattern="/login.html*" security="none" />
<http pattern="/loginUser.html*" security="none" />
<!-- USER -->
<http auto-config="true" authentication-manager-ref="wsAuthenticationManager" use-expressions="true" pattern="/testUser/**">
<intercept-url pattern="/loginUser.html" access="permitAll" />
<intercept-url pattern="/testUser/**" access="hasRole('user')" />
<access-denied-handler error-page="/403" />
<form-login login-page="/loginUser.html"
authentication-failure-url="/loginUser.html?login_error=true"
default-target-url="/testUser" />
<logout invalidate-session="true" logout-success-url="/logintUser.html" />
</http>
<beans:bean id="customAuthenticationProvider" class="net.universia.test.service.CustomAuthenticationProvider" />
<!-- Acceso contra WS -->
<authentication-manager id="wsAuthenticationManager">
<authentication-provider ref="customAuthenticationProvider" />
</authentication-manager>
<!--ADMIN -->
<http auto-config="true" use-expressions="true" authentication-manager-ref="authenticationManager" >
<intercept-url pattern="/login.html" access="permitAll" />
<intercept-url pattern="/test/**" access="hasRole('admin')" />
<intercept-url pattern="/member/**" access="hasAnyRole('moderator','admin')" />
<intercept-url pattern="/testUser/**" access="hasRole('admin')" />
<access-denied-handler error-page="/403" />
<form-login login-page="/login.html"
authentication-failure-url="/login.html?login_error=true"
username-parameter="j_username" password-parameter="j_password"/>
<logout invalidate-session="true" logout-success-url="/loginUser.html" />
<remember-me user-service-ref="customUserDetailsService" />
</http>
<beans:bean id="customUserDetailsService" class="net.universia.test.service.CustomUserDetailsService" />
<beans:bean id="md5PasswordEncoder"
class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" />
<!-- Acceso contra base de datos -->
<authentication-manager alias="authenticationManager" id="authenticationManager">
<authentication-provider user-service-ref="customUserDetailsService">
<password-encoder hash="md5" />
</authentication-provider>
</authentication-manager>
</beans:beans>
CustomUserDetailService para administrator:
#Service
#Transactional(readOnly=true)
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private HibernateTestAdminDaoImpl userDAO;
public UserDetails loadUserByUsername(String login)throws UsernameNotFoundException {
TestAdmin userAdmin = null;
try {
userAdmin = userDAO.getTestAdmin(login);
} catch (BussinessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
boolean enabled = true;
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
return new User(
userAdmin.getLoginAdmin(),
userAdmin.getPasswordAdmin(),
enabled,
accountNonExpired,
credentialsNonExpired,
accountNonLocked,
getAuthorities(userAdmin.getRole().getIdRole())
);
}
public Collection<? extends GrantedAuthority> getAuthorities(Integer role) {
List<GrantedAuthority> authList = getGrantedAuthorities(getRoles(role));
return authList;
}
public List<String> getRoles(Integer role) {
List<String> roles = new ArrayList<String>();
if (role.intValue() == 1) {
roles.add("admin");
roles.add("moderator");
} else if (role.intValue() == 2) {
roles.add("moderator");
}
return roles;
}
public static List<GrantedAuthority> getGrantedAuthorities(List<String> roles) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
for (String role : roles) {
authorities.add(new SimpleGrantedAuthority(role));
}
return authorities;
}
}
CustomAuthenticationProvider user:
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
private HibernateTestUsuarioDaoImpl userDAO;
UniversiaUser usw;
public CustomAuthenticationProvider() {
super();
}
// Retorna credenciales del usuario web service
public Authentication authenticate(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
/*
final String loginUser = authentication.getName();
final String password = authentication.getCredentials().toString();
try {
usw = userDAO.loginUserWS(loginUser, password);
} catch (UserRegistryWSException e) {
String errorCode = e.getLocalizedMessage();
System.out.print(errorCode);
} catch (Exception e) {
UsuarioUniversiaException ee = new UsuarioUniversiaException(
UsuarioUniversiaException.FERIA_VIRTUAL_USER_ERROR_LOGIN,
e);
ee.setLogin(loginUser);
throw ee;
}
if (usw.getEmail().equals("loginUser")) {
final List<GrantedAuthority> grantedAuths = new ArrayList<>();
grantedAuths.add(new SimpleGrantedAuthority("user"));
final UserDetails principal = new User(loginUser, password, grantedAuths);
final Authentication auth = new UsernamePasswordAuthenticationToken(principal, password, grantedAuths);
return auth;
} else {
return null;
}
*/
//Test parameters
final String loginUser = request.getParameter("username");
final String password = request.getParameter("password");
if (loginUser.equals("admin") && password.equals("system")) {
final List<GrantedAuthority> grantedAuths = new ArrayList<>();
grantedAuths.add(new SimpleGrantedAuthority("user"));
final UserDetails principal = new User(loginUser, password, grantedAuths);
final Authentication auth = new UsernamePasswordAuthenticationToken(principal, password, grantedAuths);
return auth;
} else {
return null;
}
}
#Override
public boolean supports(final Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// TODO Auto-generated method stub
return null;
}
}
In customautheticationprovider discuss what comes from the webservice and send test parameters
Thanks and any help is welcome
Now I have two running !!!
One customAuthenticationProvider user and customAuthenticationDetailService for Administrator and implement each filter
I am new to spring security. I have two user roles like Admin and Common Users. I want to access some jsp only access by the admin users, but the problem is once a user is log out he/she still can access the jsp page which i put restricted in spring security config.
Let me know what i am doing here is the correct or not?
Thank you
spring_security.xml
<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.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<http auto-config="true">
<intercept-url pattern="/admin/**" access="ROLE_ADMIN" />
<intercept-url pattern="/user/**" access="ROLE_USER" />
<form-login login-page="/login" default-target-url="/welcome"
authentication-failure-url="/loginfailed" />
<logout logout-success-url="/logout" />
</http>
<beans:bean id="customUserDetailsService"
class="com.nikunj.javabrains.services.CustomUserDetailsService"></beans:bean>
<authentication-manager>
<authentication-provider user-service-ref="customUserDetailsService">
</authentication-provider>
</authentication-manager>
//------------------------------
Controller
package com.nikunj.javabrains.controller;
import java.security.Principal;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.nikunj.javabrains.domain.User;
import com.nikunj.javabrains.services.UserService;
#Controller
public class UserController {
#Autowired
private UserService userService;
#RequestMapping(value = "/welcome", method = RequestMethod.GET)
public String printWelcome(ModelMap model, Principal principal,
HttpServletRequest request) {
String name = principal.getName(); // get logged in username
model.addAttribute("username", name);
model.addAttribute("message",
"Spring Security login + database example");
if (request.isUserInRole("ROLE_ADMIN")) {
return "admin_page";
}
return "common_page";
}
#RequestMapping(value = "/login", method = RequestMethod.GET)
public String login(ModelMap model) {
return "login";
}
#RequestMapping(value = "/loginfailed", method = RequestMethod.GET)
public String loginerror(ModelMap model) {
model.addAttribute("error", "true");
return "login";
}
#RequestMapping(value = "/logout", method = RequestMethod.GET)
public String logout(ModelMap model) {
return "login";
}
#RequestMapping("/regiPage")
public String regiPage(#ModelAttribute("user") User user,
BindingResult result) {
return "registration";
}
#RequestMapping(value = "/saveUser", method = RequestMethod.POST)
public String saveUserData(#ModelAttribute("user") User user,
BindingResult result) {
userService.addUser(user);
return "login";
}
}
</beans:beans>
//------------------------
CustomServiceClass
import com.nikunj.javabrains.dao.UserDao;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
#Service
#Transactional(readOnly=true)
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UserDao userDAO;
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
com.nikunj.javabrains.domain.User domainUser = userDAO.getUser(username);
boolean enabled = true;
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
System.out.println("*************************************");
System.out.println(domainUser.getId());
return new User(
domainUser.getUsername(),
domainUser.getPassword(),
enabled,
accountNonExpired,
credentialsNonExpired,
accountNonLocked,
getAuthorities(domainUser.getId())
);
}
public Collection<? extends GrantedAuthority> getAuthorities(Integer role) {
List<GrantedAuthority> authList = getGrantedAuthorities(getRoles(role));
return authList;
}
public List<String> getRoles(Integer role) {
List<String> roles = new ArrayList<String>();
if (role.intValue() == 1) {
roles.add("ROLE_ADMIN");
} else {
roles.add("ROLE_USER");
}
return roles;
}
public static List<GrantedAuthority> getGrantedAuthorities(List<String> roles) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
for (String role : roles) {
authorities.add(new SimpleGrantedAuthority(role));
}
return authorities;
}
}
//---------------------------
#Controller
public class AdminController {
#Autowired
private UserService userService;
#RequestMapping(value = "/admininput", method = RequestMethod.GET)
public String login(ModelMap model) {
System.out.println("*************************");
return "admininputpage";
}
}
Ok, so as per your last comment, the URL /admininput is being accessed by everyone.
This is the behaviour I would expect, as there is no security rules defined for this URL pattern.
In your security config you define the following rules:
<intercept-url pattern="/admin/**" access="ROLE_ADMIN" />
<intercept-url pattern="/user/**" access="ROLE_USER" />
This config will require all resources with the URL pattern /admin/** to be logged in with role ROLE_ADMIN and all resources with URL pattern /user/** to be logged in with role ROLE_USER. All other URL patterns will be permitAll.
If you want to restrict that URL you will either need to change the URL pattern, or add an intercept rule. E.g.
Change URL from /admininput to /admin/input or /admin/admininput etc
Alternatively, add an explicit intercept rule (or another pattern based rule) to cover this URL:
<intercept-url pattern="/admininput" access="ROLE_ADMIN" />
(although, not a good idea to have explicit interceptor rules for every URL! so would be better to change URL if possible to the convention you already defined)
you can use unique session id in url .if destroy session after logout or by copying url,URL can't be accessible without login URL with logged session.