Author |
Topic: Spring Security |
|
WebSpider member offline |
|
posts: |
147 |
joined: |
06/29/2006 |
from: |
Seattle, WA |
|
|
|
|
|
Spring Security |
Introduction
Spring Security provides security services for J2EE-based enterprise software applications.
Prerequsites
Eclipse with Spring Tools Suite (STS) -- link Dependencies (pom.xml):
<dependencies>
<!-- Spring -->
<!-- ... other dependency elements ... -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>4.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>4.2.3.RELEASE</version>
</dependency>
<dependency> <!-- optional, LDAP related -->
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-ldap</artifactId>
<version>4.2.3.RELEASE</version>
</dependency>
</dependencies>
|
|
|
|
|
|
|
WebSpider member offline |
|
posts: |
147 |
joined: |
06/29/2006 |
from: |
Seattle, WA |
|
|
|
|
|
Spring Security -- Configuration |
/WEB-INF/spring/spring-security.xml
Example #1: Role based + In-Memory authentication
<!-- AUTHORIZATION -->
<http pattern="/img/**" security="none" />
<http auto-config="true">
<intercept-url pattern="/admin/*" access="ROLE_ADMIN" />
<intercept-url pattern="/app/**/*" access="ROLE_USER" />
</http>
<!-- AUTHENTICATION (in memory) -->
<authentication-manager>
<authentication-provider>
<user-service>
<user name="john" password="john_pass" authorities="ROLE_USER" />
<user name="lisa" password="lisa_pass" authorities="ROLE_USER, ROLE_ADMIN" />
</user-service>
</authentication-provider>
</authentication-manager>
Example #2: Expression-Based Access Control + LDAP authentication
<!-- AUTHORIZATION -->
<http pattern="/img/*" security="none" />
<http auto-config="true" use-expressions="true">
<intercept-url pattern="/img/*" access="permitAll" />
<intercept-url pattern="/app/**/*" access="isAuthenticated()" />
<intercept-url pattern="/**/*" access="permitAll" />
</http>
<!-- AUTHENTICATION (LDAP) -->
<authentication-manager>
<authentication-provider ref="ldapActiveDirectoryAuthProvider"></authentication-provider>
</authentication-manager>
<beans:bean id="ldapActiveDirectoryAuthProvider"
class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
<beans:constructor-arg value="abc.xyz.com"></beans:constructor-arg>
<beans:constructor-arg value="ldaps://ad.abc.xyz.com:636"></beans:constructor-arg>
</beans:bean>
The most common built-in expressions: hasRole([role]) hasAnyRole([role1,role2]) hasAuthority([authority]) hasAnyAuthority([authority1,authority2]) principal -- allows direct access to the Principal object authentication -- allows direct access to the Authentication object permitAll denyAll isAnonymous() isRememberMe() -- returns true if the current principal is a remember-me user isAuthenticated() -- !isAnonymous() isFullyAuthenticated() -- !(isAnonymous()||isRememberMe()) hasPermission(Object target, Object permission) -- hasPermission(domainObject, 'read') hasPermission(Object targetId, String targetType, Object permission) -- hasPermission(1, 'com.example.Message', 'read')
|
|
|
|
|
|
|
WebSpider member offline |
|
posts: |
147 |
joined: |
06/29/2006 |
from: |
Seattle, WA |
|
|
|
|
|
/WEB-INF/web.xml |
<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/*.xml</param-value>
</context-param>
<!-- LISTENERS -->
<!-- Creates the Spring Container shared by all Servlets and Filters -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- FILTERS -->
<!-- Creates the Spring Security filters shared by all Servlets and Filters -->
<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>
<!-- SERVLETS -->
<servlet>
<servlet-name>myServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/myServlet/dispatch-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>myServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
With the above xml, the Spring Framework will do: Setting a filter which is handled by DelegatingFilterProxy which is going to delegate the job to bean named springSecurityFilterChain springSecurityFilterChain is a built-in default name which is implemented by <security:http> element, injected by spring-security.xml. spring-security.xml is to load via ContextLoaderListener when ServletContext is initialized.
|
|
|
|
|
|
|
WebSpider member offline |
|
posts: |
147 |
joined: |
06/29/2006 |
from: |
Seattle, WA |
|
|
|
|
|
Example #3: Expression-Based Access Control + LDAP authentication + Custom Login Page |
Step 1: Custom login -- Controller
GET http://<host>:<port>/<context>/my_login --> this.login(String error, String logout)
@RequestMapping(value = "/my_login", method = RequestMethod.GET)
public ModelAndView login(
@RequestParam(value = "error", required = false) String error,
@RequestParam(value = "logout", required = false) String logout) {
ModelAndView model = new ModelAndView();
if (error != null) {
model.addObject("error", "Invalid username and password!");
}
if (logout != null) {
model.addObject("msg", "You've been logged out successfully.");
}
model.setViewName("login");
return model;
}
Step 2: Custom login -- View
model("login") --> login.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<body onload='document.loginForm.username.focus();'>
<h1>Spring Security Custom Login Form (XML)</h1>
<div>
<h2>Login with Username and Password</h2>
<c:if test="${not empty error}">
<div class="error">${error}</div>
</c:if>
<c:if test="${not empty msg}">
<div class="msg">${msg}</div>
</c:if>
<c:url var="loginProcessUrl" value="/where_to_process_login" />
<form name='loginForm' action="${loginProcessUrl}" method='POST'>
<table>
<tr>
<td>User:</td>
<td><input type='text' name='username' value=''></td>
</tr>
<tr>
<td>Password:</td>
<td><input type='password' name='password' /></td>
</tr>
<tr>
<td colspan='2'><input name="submit" type="submit"
value="submit" /></td>
</tr>
</table>
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
</form>
</div>
</body>
</html>
Step 3: Custom login -- Configure
<!-- AUTHORIZATION -->
<http pattern="/img/*" security="none" />
<http auto-config="true" use-expressions="true">
<intercept-url pattern="/img/*" access="permitAll" />
<intercept-url pattern="/app/**/*" access="isAuthenticated()" />
<intercept-url pattern="/**/*" access="permitAll" />
<form-login
login-page='/my_login' <--1-- How to get here: GET /<context>/my_login
username-parameter="username" <----- default "username"
password-parameter="password" <----- default "password"
login-processing-url="/where_to_process_login" <--2-- where to process?
authentication-failure-url="/my_login?error" <--3-- where to go if error?
default-target-url="/welcome" <--4-- where to go if success?
always-use-default-target="false" <--5-- where to go if success? (true)?
default-target-url|user-target-url
/>
<logout
logout-url="/where_to_process_logout" <--1-- where to process?
logout-success-url="/my_login?logout" <--2-- where to go if success?
/>
<!-- enable csrf protection -->
<csrf/>
</http>
<!-- AUTHENTICATION (LDAP) -->
<authentication-manager>
<authentication-provider ref="ldapActiveDirectoryAuthProvider"></authentication-provider>
</authentication-manager>
<beans:bean id="ldapActiveDirectoryAuthProvider"
class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
<beans:constructor-arg value="abc.xyz.com"></beans:constructor-arg>
<beans:constructor-arg value="ldaps://ad.abc.xyz.com:636"></beans:constructor-arg>
</beans:bean>
|
|
|
|
|
|
|
WebSpider member offline |
|
posts: |
147 |
joined: |
06/29/2006 |
from: |
Seattle, WA |
|
|
|
|
|
Custom login -- Flow |
Ultimate goal: GET /app/list_resource
user -------------------- /app/list_resource -------------------------------------------------> protected resource
How to reach the goal: GET /app/list_resource
user --------------- /app/list_resource
|
v
filter: DelegatingFilterProxy
|
v
<http form-login@login-page: /my_login (1)
|
v
Controller.login(String error, String logout)
|
v
View: login.jsp action="/where_"
(user's input) -------------------> match (2) in xml? --no--> (custom process)
|
yes
|
v
processed by Spring: <authentication-manager>
|
succeed? --no--> /my_login?error (3)
|
yes
|
v
always-use-default-target (5) ==true? --no--> /app/list_resource -----> protected resource
|
yes
|
v
/welcome (4)
|
|
|
|
|
|
|
WebSpider member offline |
|
posts: |
147 |
joined: |
06/29/2006 |
from: |
Seattle, WA |
|
|
|
|
|
Two ways to provide custom login/authentication |
user --------------- /app/list_resource
|
v
filter: DelegatingFilterProxy
|
v
<http form-login@login-page: /my_login (1)
|
v
Controller.login(String error, String logout)
|
v
View: login.jsp action="/where_?"
(user's input) -------------------> match (2) in xml? --no--> A: (custom process)
|
yes
|
v
B: processed by Spring: <authentication-manager>
|
As shown in the above flow chart, there are two possible routes to process authentication:
Route A: If "/where_?" in login.jsp does NOT match "/where_to_process_login" in xml configuration, the traffic is going to flow to custom process controller:
@RequestMapping(value = "/where_to_process_login_custom", method = RequestMethod.POST)
public ModelAndView login_process(@RequestParam String username, @RequestParam String password, HttpSession session){
/* your custom implementation here ... */
}
Route B: If "/where_?" in login.jsp does match "/where_to_process_login" in xml configuration, the traffic is going to flow to spring process and you can still add your own custom implementation by providing your own autentication-provider:
@Configuration
@EnableWebSecurity
public class DBSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
/* http.authorizeRequests.configuration ... */
}
// XML counterpart (if not defined here by WebSecurityConfigurerAdapter):
// <authentication-manager>
// <authentication-provider ref="customAuthenticationProvider" />
// </authentication-manager>
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.authenticationProvider(
getAuthenticationProvider() // <-- your own provider here
);
}
@Bean
public DaoAuthenticationProvider getAuthenticationProvider(){
DaoAuthenticationProvider auth = new DaoAuthenticationProvider();
auth.setUserDetailsService(userDetailsService);
auth.setPasswordEncoder(passwordEncoder());
return auth;
}
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
|
|
|
|
|
|
|
|