we have a spring MVC application and I think there is some misconfiguration about session or cookies management basically we have that config about resources, session config and security
XML config:
<security:http auto-config=
"false" use-expressions="true"
disable-url-rewriting="true" entry-point-ref="authenticationEntryPoint"
create-session="ifRequired">
<security:headers>
<security:frame-options policy="SAMEORIGIN"/>
</security:headers>
<security:csrf disabled="true"/>
<security:custom-filter ref="sessionFilter" before="SESSION_MANAGEMENT_FILTER" />
<security:custom-filter before="PRE_AUTH_FILTER" ref="openIdConnectAuthenticationFilter" />
<security:intercept-url pattern="/resources/**" access="permitAll()"/>
...
</security:http>
Session Filter:
public class SessionFilter implements Filter {
private boolean httpOnly=false;
private boolean secure=false;
public SessionFilter(boolean httpOnly, boolean secure) {
this.httpOnly = httpOnly;
this.secure = secure;
}
public SessionFilter() {
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(
ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
Cookie[] allCookies = req.getCookies();
if (allCookies != null && !"self-health-check".equals(req.getHeader("User-Agent"))) {
Cookie session =
Arrays.stream(allCookies).filter(x -> x.getName().equals("JSESSIONID"))
.findFirst().orElse(null);
if (session != null) {
session.setHttpOnly(httpOnly);
session.setSecure(secure);
res.addCookie(session);
}
}
chain.doFilter(req, res);
}
#Override
public void destroy() {
}
}
And the client is asking this question; why there are a lot of endpoints of sessions?
I don't know it is normal or not.
help, please!
I found the solution after some month. The problem is not about my spring MVC config, it is about web.xml config. we needed to add a tracking-mode setting
<session-config>
<tracking-mode>COOKIE</tracking-mode>
</session-config>
Related
My spring boot (2.2.4) application is using siteminder as authentication provider. Upon successful authentication siteminder sends user details (Roles) in Http header. Inside the application I need to use spring security for role based authorization. Here is reference documentation I am following -
https://docs.spring.io/spring-security/site/docs/5.0.x/reference/html/preauth.html
My spring-security.xml will look this.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
https://www.springframework.org/schema/security/spring-security.xsd">
<security:http>
<!-- Additional http configuration omitted -->
<security:csrf disabled="true"/>
<security:intercept-url pattern="/**" access="hasRole('ROLE_ABC')"/>
<security:custom-filter position="PRE_AUTH_FILTER" ref="siteminderFilter" />
</security:http>
<bean id="siteminderFilter" class="org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter">
<property name="principalRequestHeader" value="SM_USER"/>
<property name="authenticationManager" ref="authenticationManager" />
</bean>
<bean id="preauthAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
<property name="preAuthenticatedUserDetailsService">
<bean id="userDetailsServiceWrapper"
class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
<property name="userDetailsService" ref="userDetailsService"/>
</bean>
</property>
</bean>
<bean id="userDetailsService" class="com.mycode.service.SCPMUserDetailsService"/>
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="preauthAuthProvider" />
</security:authentication-manager>
</beans>
How do I create a Java config file which will incorporate above security for Spring boot application.
I started something like this and need to inject all the pieces in above xml file.
#EnableWebSecurity
#Order(SecurityProperties.BASIC_AUTH_ORDER - 10)
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {
#Autowired
private MyUserDetailsService myUserDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()// Disable for GraphIQL to work locally
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/healthcheck/**").permitAll()
.antMatchers("/scpm/**").permitAll()
.antMatchers("/approvers/**").hasRole("APPROVER")
.antMatchers("/departments/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and().httpBasic();
}
}
Spring Security reference document does not provide Java Code for SiteMinder integration. It just provides xml configuration for non-spring boot applications. Here is the Java code which can be used in any Spring Boot Application.
#EnableWebSecurity
#Configuration
#Order(SecurityProperties.BASIC_AUTH_ORDER - 10)
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {
#Autowired
private MyUserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
auth.authenticationProvider(preAuthenticatedAuthenticationProvider());
}
#Bean
public RequestHeaderAuthenticationFilter siteminderFilter() throws Exception {
RequestHeaderAuthenticationFilter filter = new RequestHeaderAuthenticationFilter();
filter.setPrincipalRequestHeader("SM_USER");
filter.setAuthenticationManager(this.authenticationManager());
return filter;
}
#Bean
public PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider() {
PreAuthenticatedAuthenticationProvider preauthAuthProvider = new PreAuthenticatedAuthenticationProvider();
preauthAuthProvider.setPreAuthenticatedUserDetailsService(userDetailsByNameServiceWrapper());
return preauthAuthProvider;
}
#Bean
public UserDetailsByNameServiceWrapper userDetailsByNameServiceWrapper() {
UserDetailsByNameServiceWrapper userDetailsServiceWrapper = new UserDetailsByNameServiceWrapper();
userDetailsServiceWrapper.setUserDetailsService(userDetailsService);
return userDetailsServiceWrapper;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilter(siteminderFilter())
.csrf().disable()// Disable for GraphIQL to work locally
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/healthcheck/**").permitAll()
.antMatchers("/approvers/**").hasRole("APPROVER")
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and().httpBasic();
}
}
Here is custom UserDetailsService.
#Service
public class MyUserDetailsService implements UserDetailsService {
private Logger logger = LoggerFactory.getLogger(SCPMUserDetailsService.class);
#Autowired
private HttpServletRequest request;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
HttpServletRequest request =
((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
String csgroups = "";
Collection<GrantedAuthority> auth = new ArrayList<>();
try {
username = request.getHeader("SM_USER");
} catch (Exception ex){
logger.trace("SM_USER not found in Request Http Headers");
ex.printStackTrace();
}
try {
csgroups = request.getHeader("ROLES");;
} catch (Exception ex){
logger.trace("CSGROUPS not found in Request Http Headers");
ex.printStackTrace();
}
//Roles
if(csgroups != null) {
StringTokenizer st = new StringTokenizer(csgroups, "^");
while (st.hasMoreElements()) {
GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_" + st.nextToken());
}
}
return new User(username, "", true, true, true, true,auth);
}
#Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
My config in applicationContext.xml is as follows:
<mvc:annotation-driven/>
<context:component-scan base-package="com.lixindi.gradproject"/>
<mvc:resources mapping="login/**" location="login/"/>
<mvc:resources mapping="admin/**" location="admin/"/>
<mvc:resources mapping="vote/**" location="vote/"/>
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/admin/**"/>
<bean class="com.lixindi.gradproject.interceptor.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
LoginInterceptor:
public class LoginInterceptor extends HandlerInterceptorAdapter {
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
if (httpServletRequest.getSession().getAttribute("user") == null) {
httpServletResponse.sendRedirect("/login/login.html");
System.out.println("invoked");
return false;
} else {
return true;
}
}
}
There are several html files under webapp/admin which I do not want to go through DispatcherServlet. And the interceptor does not intercept requests, for example, http://localhost:8080/admin/admin.html. But I want any url including resources to be intercepted to make sure users are logged in. How could I make this work?
Thanks #Serge Ballesta.
However, it still cannot intercept resources, for example, http://localhost:8080/admin/admin.html.
web.xml:
<filter>
<filter-name>LoginFilter</filter-name>
<filter-class>com.lixindi.gradproject.filter.LoginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>LoginFilter</filter-name>
<url-pattern>/admin/*</url-pattern>
</filter-mapping>
LoginFilter:
public class LoginFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
System.out.println("filter");
if (request.getSession().getAttribute("user") == null) {
response.sendRedirect("/login/login.html");
} else {
filterChain.doFilter(servletRequest, servletResponse);
}
}
public void destroy() {
}
}
Interceptors are a kind of private filters which are called by the SpringMVC machinery around the controller (or even after the rendering phase). So it makes no sense to ask for an interceptor to be called for an URL not processed by DispatcherServlet which is the entry point of that machinery.
You will have to use a plain Java-EE Filter here, because filters are called directly by the servlet container
Following is my sample Spring Security configuration.
I want all /api to return HTTP 401 code instead of redirecting 302 to login page.
Also I want to keep the redirect feature for old web pages.
<security:http auto-config='true' use-expressions="true" >
<security:intercept-url pattern="/api*" access="hasRole('USER')" />
<security:intercept-url pattern="/oldweb*" access="hasRole('USER')" />
<security:form-login login-page="/login.jsp" authentication-failure-url="/login.jsp?login_error=1" default-target-url="/home"/>
</security:http>
I came to more simpler solution.
In Spring Boot and Java config you just have to register additional entry point in addition to default one. And because all your rest-services resides in "/api" name space, you could use AntPathRequestMatcher("/api/**") to match necessary requests.
So, final solution is:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
//Actually Spring already configures default AuthenticationEntryPoint - LoginUrlAuthenticationEntryPoint
//This one is REST-specific addition to default one, that is based on PathRequest
.defaultAuthenticationEntryPointFor(getRestAuthenticationEntryPoint(), new AntPathRequestMatcher("/api/**"));
}
private AuthenticationEntryPoint getRestAuthenticationEntryPoint() {
return new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED);
}
}
You need to have a custom authentication entry point.
public class CustomEntryPoint extends LoginUrlAuthenticationEntryPoint {
private static final String XML_HTTP_REQUEST = "XMLHttpRequest";
private static final String X_REQUESTED_WITH = "X-Requested-With";
public CustomEntryPoint(String loginFormUrl) {
super(loginFormUrl);
}
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException {
if (XML_HTTP_REQUEST.equals(request.getHeader(X_REQUESTED_WITH))) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
} else {
super.commence(request, response, exception);
}
}
}
Finally change your config to this:
<security:http auto-config='true' use-expressions="true" entry-point-ref="customEntryPoint">
<security:intercept-url pattern="/api*" access="hasRole('USER')" />
<security:intercept-url pattern="/oldweb*" access="hasRole('USER')" />
<security:form-login login-page="/login.jsp" authentication-failure-url="/login.jsp?login_error=1" default-target-url="/home"/>
<beans:bean id="customEntryPoint" class="CustomEntryPoint">
<beans:constructor-arg value="/login"/>
</beans:bean>
</security:http>
When text/html is not acceptable, return 401.
#EnableWebSecurity
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(final HttpSecurity http) throws Exception {
// ...
final RequestMatcher matcher = new NegatedRequestMatcher(
new MediaTypeRequestMatcher(MediaType.TEXT_HTML));
http.exceptionHandling()
.defaultAuthenticationEntryPointFor(
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED), matcher);
}
}
I am currently moving from an xml-based configuration of spring security to a java based one. I need to setup a custom WebAuthenticationDetails via Java. Is there a way to do that? In XML, I would just set that authenticationDetailsSource of the UsernamePasswordAuthenticationFilter. Relevant sample below
<http entry-point-ref="loginUrlAuthenticationEntryPoint">
<custom-filter ref="rememberMeFilter" position="REMEMBER_ME_FILTER"/>
<custom-filter ref="loginFilter" position="FORM_LOGIN_FILTER"/>
<intercept-url pattern="/access/**" access="ROLE_USER" />
<csrf/>
<access-denied-handler error-page="/login" />
<logout logout-success-url="/login?logout" />
</http>
<beans:bean id="myWebAuthDetails"
class="com.auth.CustomWebAuthenticationDetailsSource">
</beans:bean>
<beans:bean id="loginFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<beans:property name="authenticationManager" ref="authenticationManager" />
<beans:property name="rememberMeServices" ref="rememberMeServices" />
<beans:property name="usernameParameter" value="username" />
<beans:property name="passwordParameter" value="password" />
<beans:property name="sessionAuthenticationStrategy" ref="sas" />
<beans:property name="allowSessionCreation" value="true" />
<beans:property name="authenticationDetailsSource" ref="myWebAuthDetails" />
<beans:property name="authenticationFailureHandler" ref="failureHandler" />
<beans:property name="authenticationSuccessHandler" ref="successHandler" />
<beans:property name="filterProcessesUrl" value="/processlogin" />
</beans:bean>
Please find the configuration below. I have mapped your xml configuration to Java config.
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/", "/home").permitAll()
.anyRequest().authenticated().and().formLogin()
.authenticationDetailsSource(authenticationDetailsSource())
.successHandler(authenticationSuccessHandler())
.failureHandler(failureHandler()).loginPage("/login")
.usernameParameter("usernameCustom")
.passwordParameter("passwordCustom").permitAll().and().logout()
.permitAll().and().rememberMe()
.rememberMeServices(rememberMeServices());
}
private AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> authenticationDetailsSource() {
return new AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails>() {
#Override
public WebAuthenticationDetails buildDetails(
HttpServletRequest request) {
return new WebAuthenticationDetails(request);
}
};
}
#Bean
RememberMeServices rememberMeServices() {
RememberMeServices rememberMeServices = new RememberMeServices() {
#Override
public void loginSuccess(HttpServletRequest arg0,
HttpServletResponse arg1, Authentication arg2) {
}
#Override
public void loginFail(HttpServletRequest arg0,
HttpServletResponse arg1) {
}
#Override
public Authentication autoLogin(HttpServletRequest arg0,
HttpServletResponse arg1) {
return null;
}
};
return rememberMeServices;
}
#Bean
AuthenticationFailureHandler failureHandler() {
return new AuthenticationFailureHandler() {
#Override
public void onAuthenticationFailure(HttpServletRequest req,
HttpServletResponse res, AuthenticationException arg2)
throws IOException, ServletException {
req.setAttribute("error", "forward");
req.getRequestDispatcher("/homedefault").forward(req, res);
}
};
}
#Bean
AuthenticationSuccessHandler authenticationSuccessHandler() {
return new AuthenticationSuccessHandler() {
#Override
public void onAuthenticationSuccess(HttpServletRequest req,
HttpServletResponse res, Authentication arg2)
throws IOException, ServletException {
res.sendRedirect("homedefault");
}
};
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password")
.roles("USER");
}
}
I am a newbie to Spring Security 3. I am using roles for users to login.
I want to add some session value after a user is authorized into the application. Maybe I need some filter so that it redirects to my method which adds some session value. I have configured my security.xml file but I am not sure whether I am doing right things. Any examples in that direction would help. Which Filter Class should I use? How should I configure security.xml file?
<custom-filter ref="authenticationFilter" after="FORM_LOGIN_FILTER "/>
<beans:bean id="authenticationFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<beans:property name="filterProcessesUrl" value="/j_spring_security_check" />
<beans:property name="authenticationManager" ref="authenticationManager" />
<beans:property name="authenticationSuccessHandler" ref="successHandler" />
</beans:bean>
<beans:bean id="successHandler" class="org.dfci.sparks.datarequest.security.CustomAuthorizationFilter"/>
My filter class method I need to add some session value.
public class CustomAuthorizationFilter implements AuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
Set<String> roles = AuthorityUtils.authorityListToSet(authentication
.getAuthorities());
if (roles.contains("ROLE_USER")) {
request.getSession().setAttribute("myVale", "myvalue");
}
}
}
Edit Code
I have modified my security.xml file and class file
<custom-filter ref="authenticationFilter" after="FORM_LOGIN_FILTER "/>
public class CustomAuthorizationFilter extends GenericFilterBean {
/*
* ServletRequestAttributes attr = (ServletRequestAttributes)
* RequestContextHolder.currentRequestAttributes(); HttpSession
* session=attr.getRequest().getSession(true);
*/
#Autowired
private UserService userService;
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
try {
chain.doFilter(request, response);
HttpServletRequest req = (HttpServletRequest) request;
HttpSession session = req.getSession(true);
Authentication authentication = SecurityContextHolder
.getContext().getAuthentication();
Set<String> roles = AuthorityUtils
.authorityListToSet(authentication.getAuthorities());
User user = null;
if (true) {
session.setAttribute("Flag", "Y");
}
}
} catch (IOException ex) {
throw ex;
}
}
}
Which invokes each and every URL. Is it any alternative to call filter method only once when a user is authenticated?
Finally I was able to resolved my problem. Instead of using filter I have added handler which only invokes for successful login.
Following line is added in security.xml
<form-login login-page="/" authentication-failure-url="/?login_error=1" default-target-url="/" always-use-default-target="false"
authentication-success-handler-ref="authenticationSuccessHandler"/>
<logout />
<beans:bean id="authenticationSuccessHandler" class="security.CustomSuccessHandler"/>
Also I have added one custom handler which add session attribute.
package security;
import java.io.IOException;
import java.security.GeneralSecurityException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
public class CustomSuccessHandler extends
SavedRequestAwareAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(final HttpServletRequest request,
final HttpServletResponse response, final Authentication authentication)
throws IOException, ServletException {
super.onAuthenticationSuccess(request, response, authentication);
HttpSession session = request.getSession(true);
try {
if (CurrentUser.isUserInRole("USER")) {
session.setAttribute("Flag", "user");
}
} catch (Exception e) {
logger.error("Error in getting User()", e);
}
}
}
You can use standart java filter (I mean implement Filter interface). Just place it after authentification filter in web.xml (this means that it will go later in the filter chain and will be called after security filter chain).
public class CustomFilter implements Filter{
#Override
public void destroy() {
// Do nothing
}
#Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Set<String> roles = AuthorityUtils.authorityListToSet(authentication.getAuthorities());
if (roles.contains("ROLE_USER")) {
request.getSession().setAttribute("myVale", "myvalue");
}
chain.doFilter(req, res);
}
#Override
public void init(FilterConfig arg0) throws ServletException {
// Do nothing
}
}
Fragment of web.xml:
<!-- The Spring Security Filter Chain -->
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<!-- Pay attention to the url-pattern -->
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
<!-- <dispatcher>FORWARD</dispatcher>
<dispatcher>REQUEST</dispatcher> -->
</filter-mapping>
<!-- Your filter definition -->
<filter>
<filter-name>customFilter</filter-name>
<filter-class>com.yourcompany.test.CustomFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>customFilter</filter-name>
<url-pattern>/VacationsManager.jsp</url-pattern>
</filter-mapping>