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.
Related
In the Spring Security, they automatically match input username and password with the DB saved username and password. It returns bad credential when password is not matched. However, in my code, as long as I know, if the username also does not match with DB saved username they should return bad credential. However, in my code, bad credential are shown only when username is correct and password is wrong and when username is not in DB it throws usernmaenotfoundexception.
I know I wrote usernamenotfoundexception in loaduserByusername. However, should it be first to check from DB whether username is matched and if it is wrong then bad credentials should be returned? I found userdetailservice from internet and most of people wrote the code this way. Then how should we know id not matched.,,?
Thank you so much
UserDeatilSerivce.java
package com.group6.shopping.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import com.group6.shopping.members.dao.MembersDAO;
import org.springframework.ui.Model;
public class CustomMemDetailsService implements UserDetailsService{
#Autowired
MembersDAO membersDAO;
#Override
public UserDetails loadUserByUsername(String memId) {
System.out.println("loaduserByusername");
CustomMemDetails members = null;
try {
members = membersDAO.getMemById(memId);
if(members == null) {
throw new UsernameNotFoundException("username " + memId + " not found");
}
System.out.println("**************Found user***************");
System.out.println("id : " + members.getUsername());
return members;
} catch (Exception e) {
e.printStackTrace();
}
return members;
}
}
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:security="http://www.springframework.org/schema/security"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="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/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<security:http pattern="/resources/**" security="none"/>
<security:http>
<security:intercept-url pattern="/everyone/**" access="permitAll"/>
<security:intercept-url pattern="/member/**" access="hasRole('ROLE_MEMBER')"/>
<security:intercept-url pattern="/admin/**" access="hasRole('ROLE_ADMIN')"/>
<security:form-login login-page="/everyone/login"
login-processing-url="/everyone/login/loginProcess"
default-target-url="/"
authentication-failure-handler-ref="loginFailureHandler"/>
<!-- 최대 한 개의 세션만 생성되도록 -->
<security:session-management invalid-session-url="/everyone/login">
<security:concurrency-control max-sessions="1"
expired-url="/everyone/login"
error-if-maximum-exceeded="true" />
</security:session-management>
</security:http>
<security:authentication-manager>
<security:authentication-provider user-service-ref="customMemService">
<security:password-encoder hash="bcrypt"/>
</security:authentication-provider>
</security:authentication-manager>
<bean id="bcryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />
<bean id="loginFailureHandler" class="com.group6.shopping.security.LoginFailureHandler"/>
<context:component-scan base-package="com.group6.shopping.security" />
<bean id="customMemService" class="com.group6.shopping.security.CustomMemDetailsService" />
</beans>
LoginFailureHandler.java
package com.group6.shopping.security;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class LoginFailureHandler implements AuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
String msg = "";
if(exception instanceof UsernameNotFoundException){
System.out.println("username error");
msg = "Wrong id or password. Please re-enter";
}else if (exception instanceof BadCredentialsException){
System.out.println("bad credential");
msg = "Wrong id or password. Please re-enter";
}
request.setAttribute("msg", msg);
request.getRequestDispatcher("/everyone/login?error").forward(request, response);
}
}
I had miss understanding of spring security flow. It actually checks id and password after userdetailservice is ran. So, I just delete throws new UsernameNotFoundException and return new CustomMemDetails();
then in login failure handler.java just check exception is DisabledException
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.
I am using a Spring Security, Restful WebService and Postman Rest Clien. i want to test my Restful WebService using the Postman without logging in and I do put a #Secured("AMIN_ROLE") to my methods for Restful webservice in order to secured the method, and using the POSTMAN Authorization tab i also put a Digest Auth which is the username and password with already MD5 format. now my problem is I cannot test my restful webservice using the Postman it will prompt a UNAUTHORIZED. please help me for this stuff...
security.xml
<security:http
use-expressions="true"
entry-point-ref="authEntryPoint">
<security:intercept-url pattern="/" access="permitAll" />
<security:intercept-url pattern="/index.jsp" access="permitAll" />
<security:intercept-url pattern="/**" access="isAuthenticated()" />
</security:http>
</security:http>
<security:authentication-manager alias="authManager">
<security:authentication-provider ref="authProvider"/>
</security:authentication-manager>
<bean id="authProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="userDetailsService"/>
<property name="passwordEncoder" ref="md5Encoder"/>
</bean>
<bean id="md5Encoder" class="org.springframework.security.authentication.encoding.Md5PasswordEncoder"/>
<bean id="userDetailsService" class="com.shindi.ippan.service.UserAccountService"/>
UserAccountService.java
#Service("userAccountService")
public class UserAccountService extends AbstractService implements UserDetailsService{
#Autowired
#Qualifier("userAccountRepository")
IRepository<UserAccount> repository;
#Override
protected IRepository<UserAccount> getRepository() {
return repository;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserAccount userAccount = ((UserAccountRepository)repository).readByUsername(username);
return userAccount;
}
}
UserAccount.java implements UserDetails
ApplicationAuthenticationEntryPoint.java
#Component("authEntryPoint")
public class ApplicationAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException ae) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "MENSAHE UNAUTHORIZED");
}
}
web.xml
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
UserAccountController.java
#Controller
#RequestMapping("/userAccount")
public class UserAccountController extends AbstractController {
#Autowired
#Qualifier("userAccountService")
IService<UserAccount> service;
#Override
public IService<UserAccount> getService() {
return service;
}
#RequestMapping(value="/login", method=RequestMethod.POST)
public #ResponseBody UserAccount login( String username, String password ){
UserAccount userAccount = ((UserAccountService)service).loadUserByUsername(username);
return userAccount;
}
#Secured("ADMIN_ROLE")
#RequestMapping(value="/test", method=RequestMethod.POST)
public #ResponseBody String test(){
return "test...";
}
#RequestMapping(value="/denied", method=RequestMethod.POST)
public #ResponseBody String denied(){
return "denied...";
}
}
I can't log in using spring-security.
The error is (in Mozilla)
The connection was interrupted
The connection to 127.0.0.1:8180 was interrupted while the page was loading.
The site could be temporarily unavailable or too busy. Try again in a few moments.
If you are unable to load any pages, check your computer's network connection.
If your computer or network is protected by a firewall or proxy, make sure that Firefox is permitted to access the Web.
Recently I've added a service that will get users from database. before it always was ok, but now I'm stunned. Please show me where to dig.
the url where I get this error is:
https://localhost:8180/j_spring_security_check
spring-security.xml
<http auto-config="true">
<http-basic/>
<intercept-url pattern="/sec/moderation.html" access="ROLE_MODERATOR"/>
<intercept-url pattern="/admin/*" access="ROLE_ADMIN"/>
<intercept-url pattern="/treeview" access="ROLE_ADMIN"/>
<form-login login-page="/login" default-target-url="/home" authentication-failure-url="/error"/>
<logout logout-success-url="/home"/>
</http>
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="customUserDetailsService">
<password-encoder hash="plaintext"></password-encoder>
</authentication-provider>
</authentication-manager>
CustomUserDetailsService.java
#Service
#Transactional(readOnly=true)
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UserDao userDao;
#Override
public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
UserEntity domainUser = userDao.getUser(login);
boolean enabled = true;
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
return new User(
domainUser.getLogin(),
domainUser.getPassword(),
enabled,
accountNonExpired,
credentialsNonExpired,
accountNonLocked,
getAuthorities(domainUser.getRole())
);
}
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_MODERATOR");
roles.add("ROLE_ADMIN");
} else if (role.intValue() == 2) {
roles.add("ROLE_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;
}
}
Ia there an ability to disable https for /j_spring_security_check ?
The default login page generated by spring security does not use https, so I presume you use a custom page. The requirement for https must be in the <form action="..."> element of that page.
Here is my spring security config:
<http pattern="/auth/login" security="none" />
<http pattern="/auth/loginFailed" security="none" />
<http pattern="/resources/**" security="none" />
<http auto-config="true" access-decision-manager-ref="accessDecisionManager">
<intercept-url pattern="/auth/logout" access="permitAll"/>
<intercept-url pattern="/admin/**" access="ADMINISTRATIVE_ACCESS"/>
<intercept-url pattern="/**" access="XYZ_ACCESS"/>
<form-login
login-page="/auth/login"
authentication-failure-url="/auth/loginFailed"
authentication-success-handler-ref="authenticationSuccessHandler" />
<logout logout-url="/auth/logout" logout-success-url="/auth/login" />
</http>
The authenticationSuccessHandler extends the SavedRequestAwareAuthenticationSuccessHandler ensuring that the user is redirected to the page he originally requested.
However, since /auth/login is marked as security="none", I am unable to successfully redirect the user to the homepage if he accesses the login page after being logged in. I believe this is the right user experience too.
I tried the below too but the Principal object is always null, presumably because of the security="none" attribute again.
#RequestMapping(value = "/auth/login", method = GET)
public String showLoginForm(HttpServletRequest request, Principal principal) {
if(principal != null) {
return "redirect:/";
}
return "login";
}
I've checked the topic more deeply than last time and found that you have to determine if user is authenticated by yourself in controller. Row Winch (Spring Security dev) says here:
Spring Security is not aware of the internals of your application
(i.e. if you want to make your login page flex based upon if the user
is logged in or not). To show your home page when the login page is
requested and the user is logged in use the SecurityContextHolder in
the login page (or its controller) and redirect or forward the user to
the home page.
So solution would be determining if user requesting /auth/login is anonymous or not, something like below.
applicationContext-security.xml:
<http auto-config="true" use-expressions="true"
access-decision-manager-ref="accessDecisionManager">
<intercept-url pattern="/auth/login" access="permitAll" />
<intercept-url pattern="/auth/logout" access="permitAll" />
<intercept-url pattern="/admin/**" access="ADMINISTRATIVE_ACCESS" />
<intercept-url pattern="/**" access="XYZ_ACCESS" />
<form-login login-page="/auth/login"
authentication-failure-url="/auth/loginFailed"
authentication-success-handler-ref="authenticationSuccessHandler" />
<logout logout-url="/auth/logout" logout-success-url="/auth/login" />
</http>
<beans:bean id="defaultTargetUrl" class="java.lang.String">
<beans:constructor-arg value="/content" />
</beans:bean>
<beans:bean id="authenticationTrustResolver"
class="org.springframework.security.authentication.AuthenticationTrustResolverImpl" />
<beans:bean id="authenticationSuccessHandler"
class="com.example.spring.security.MyAuthenticationSuccessHandler">
<beans:property name="defaultTargetUrl" ref="defaultTargetUrl" />
</beans:bean>
Add to applicationContext.xml bean definition:
<bean id="securityContextAccessor"
class="com.example.spring.security.SecurityContextAccessorImpl" />
which is class
public final class SecurityContextAccessorImpl
implements SecurityContextAccessor {
#Autowired
private AuthenticationTrustResolver authenticationTrustResolver;
#Override
public boolean isCurrentAuthenticationAnonymous() {
final Authentication authentication =
SecurityContextHolder.getContext().getAuthentication();
return authenticationTrustResolver.isAnonymous(authentication);
}
}
implementing simple interface
public interface SecurityContextAccessor {
boolean isCurrentAuthenticationAnonymous();
}
(SecurityContextHolder accessing code is decoupled from controller, I followed suggestion from this answer, hence SecurityContextAccessor interface.)
And last but not least redirect logic in controller:
#Controller
#RequestMapping("/auth")
public class AuthController {
#Autowired
SecurityContextAccessor securityContextAccessor;
#Autowired
#Qualifier("defaultTargetUrl")
private String defaultTargetUrl;
#RequestMapping(value = "/login", method = RequestMethod.GET)
public String login() {
if (securityContextAccessor.isCurrentAuthenticationAnonymous()) {
return "login";
} else {
return "redirect:" + defaultTargetUrl;
}
}
}
Defining defaultTargetUrl String bean seems like a hack, but I don't have better way not to hardcode url... (Actually in our project we use <util:constant> with class containing static final String fields.) But it works after all.
You could also restrict your login page to ROLE_ANONYMOUS and set an <access-denied-handler />:
<access-denied-handler ref="accessDeniedHandler" />
<intercept-url pattern="/auth/login" access="ROLE_ANONYMOUS" />
And in your handler check if the user is already authenticated:
#Service
public class AccessDeniedHandler extends AccessDeniedHandlerImpl {
private final String HOME_PAGE = "/index.html";
#Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && !(auth instanceof AnonymousAuthenticationToken)) {
response.sendRedirect(HOME_PAGE);
}
super.handle(request, response, e);
}
}
Implement a Redirect Interceptor for this purpose:
The Interceptor (implementing HandlerInterceptor interface) check if someone try to access the login page, and if this person is already logged in, then the interceptor sends a redirect to the index page.
public class LoginPageRedirectInterceptor extends HandlerInterceptorAdapter {
private String[] loginPagePrefixes = new String[] { "/login" };
private String redirectUrl = "/index.html";
private UrlPathHelper urlPathHelper = new UrlPathHelper();
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
if (isInLoginPaths(this.urlPathHelper.getLookupPathForRequest(request))
&& isAuthenticated()) {
response.setContentType("text/plain");
sendRedirect(request, response);
return false;
} else {
return true;
}
}
private boolean isAuthenticated() {
Authentication authentication =
SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
return false;
}
if (authentication instanceof AnonymousAuthenticationToken) {
return false;
}
return authentication.isAuthenticated();
}
private void sendRedirect(HttpServletRequest request,
HttpServletResponse response) {
String encodedRedirectURL = response.encodeRedirectURL(
request.getContextPath() + this.redirectUrl);
response.setStatus(HttpStatus.SC_TEMPORARY_REDIRECT);
response.setHeader("Location", encodedRedirectURL);
}
private boolean isInLoginPaths(final String requestUrl) {
for (String login : this.loginPagePrefixes) {
if (requestUrl.startsWith(login)) {
return true;
}
}
return false;
}
}
You can keep it simple flow by access-denied-page attribute in http element or as dtrunk said to write handler for access denied as well as. the config would be like
<http access-denied-page="/403" ... >
<intercept-url pattern="/login" access="ROLE_ANONYMOUS" />
<intercept-url pattern="/user/**" access="ROLE_USER" />
<intercept-url pattern="/admin/**" access="ROLE_ADMIN" />
<form-login login-page="/login" default-target-url="/home" ... />
...
</http>
in controller for /403
#RequestMapping(value = "/403", method = RequestMethod.GET)
public String accessDenied() { //simple impl
return "redirect:/home";
}
and for /home
#RequestMapping(value = "/home", method = RequestMethod.GET)
public String home(Authentication authentication) {
// map as many home urls with Role
Map<String, String> dashBoardUrls = new HashMap<String, String>();
dashBoardUrls.put("ROLE_USER", "/user/dashboard");
dashBoardUrls.put("ROLE_ADMIN", "/admin/dashboard");
String url = null;
Collection<? extends GrantedAuthority> grants = authentication
.getAuthorities();
// for one role per user
for (GrantedAuthority grantedAuthority : grants) {
url = dashBoardUrls.get(grantedAuthority.getAuthority());
}
if (url == null)
return "/errors/default_access_denied.jsp";
return "redirect:" + url;
}
and when you make request for /admin/dashboard without logged in, it will redirect /login automatically by security
<http pattern="/login" auto-config="true" disable-url-rewriting="true">
<intercept-url pattern="/login" access="ROLE_ANONYMOUS"/>
<access-denied-handler error-page="/index.jsp"/>
</http>
You can try checking
if(SecurityContextHolder.getContext().getAuthentication() == null)
True means the user isn't authenticated, and thus can be sent to the login page. I don't know how robust/reliable this is, but it seems reasonable to try.