发怒的桔子 · 云南省纪委省监委网站· 2 月前 · |
瘦瘦的企鹅 · VMware ...· 8 月前 · |
温文尔雅的杨桃 · 3D光学传感器行业研究宝典 - 知乎· 1 年前 · |
爱热闹的作业本 · 收购国机智骏工厂 滴滴或将 6 ...· 1 年前 · |
有情有义的牛肉面 · 只好替女主嫁给未来皇帝了-第12话 有人下毒· 1 年前 · |
This section describes how Spring Security is integrated with the Servlet API. The servletapi-xml sample application demonstrates the usage of each of these methods.
The
HttpServletRequest.getRemoteUser()
will return the result of
SecurityContextHolder.getContext().getAuthentication().getName()
which is typically the current username.
This can be useful if you want to display the current username in your application.
Additionally, checking if this is null can be used to indicate if a user has authenticated or is anonymous.
Knowing if the user is authenticated or not can be useful for determining if certain UI elements should be shown or not (i.e. a log out link should only be displayed if the user is authenticated).
The
HttpServletRequest.getUserPrincipal()
will return the result of
SecurityContextHolder.getContext().getAuthentication()
.
This means it is an
Authentication
which is typically an instance of
UsernamePasswordAuthenticationToken
when using username and password based authentication.
This can be useful if you need additional information about your user.
For example, you might have created a custom
UserDetailsService
that returns a custom
UserDetails
containing a first and last name for your user.
You could obtain this information with the following:
Authentication auth = httpServletRequest.getUserPrincipal(); // assume integrated custom UserDetails called MyCustomUserDetails // by default, typically instance of UserDetails MyCustomUserDetails userDetails = (MyCustomUserDetails) auth.getPrincipal(); String firstName = userDetails.getFirstName(); String lastName = userDetails.getLastName();
It should be noted that it is typically bad practice to perform so much logic throughout your application. Instead, one should centralize it to reduce any coupling of Spring Security and the Servlet API’s.
The
HttpServletRequest.isUserInRole(String)
will determine if
SecurityContextHolder.getContext().getAuthentication().getAuthorities()
contains a
GrantedAuthority
with the role passed into
isUserInRole(String)
.
Typically users should not pass in the "ROLE_" prefix into this method since it is added automatically.
For example, if you want to determine if the current user has the authority "ROLE_ADMIN", you could use the following:
boolean isAdmin = httpServletRequest.isUserInRole("ADMIN");
This might be useful to determine if certain UI components should be displayed. For example, you might display admin links only if the current user is an admin.
The following section describes the Servlet 3 methods that Spring Security integrates with.
The HttpServletRequest.authenticate(HttpServletRequest,HttpServletResponse) method can be used to ensure that a user is authenticated. If they are not authenticated, the configured AuthenticationEntryPoint will be used to request the user to authenticate (i.e. redirect to the login page).
The
HttpServletRequest.login(String,String)
method can be used to authenticate the user with the current
AuthenticationManager
.
For example, the following would attempt to authenticate with the username "user" and password "password":
try { httpServletRequest.login("user","password"); } catch(ServletException e) { // fail to authenticateIt is not necessary to catch the ServletException if you want Spring Security to process the failed authentication attempt.
The HttpServletRequest.logout() method can be used to log the current user out.
Typically this means that the SecurityContextHolder will be cleared out, the HttpSession will be invalidated, any "Remember Me" authentication will be cleaned up, etc. However, the configured LogoutHandler implementations will vary depending on your Spring Security configuration. It is important to note that after HttpServletRequest.logout() has been invoked, you are still in charge of writing a response out. Typically this would involve a redirect to the welcome page.
The AsynchContext.start(Runnable) method that ensures your credentials will be propagated to the new Thread. Using Spring Security’s concurrency support, Spring Security overrides the AsyncContext.start(Runnable) to ensure that the current SecurityContext is used when processing the Runnable. For example, the following would output the current user’s Authentication:
final AsyncContext async = httpServletRequest.startAsync(); async.start(new Runnable() { public void run() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); try { final HttpServletResponse asyncResponse = (HttpServletResponse) async.getResponse(); asyncResponse.setStatus(HttpServletResponse.SC_OK); asyncResponse.getWriter().write(String.valueOf(authentication)); async.complete(); } catch(Exception e) { throw new RuntimeException(e);If you are using Java Based configuration, you are ready to go. If you are using XML configuration, there are a few updates that are necessary. The first step is to ensure you have updated your web.xml to use at least the 3.0 schema as shown below:
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> </web-app>Next you need to ensure that your springSecurityFilterChain is setup for processing asynchronous requests.
<filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class> org.springframework.web.filter.DelegatingFilterProxy </filter-class> <async-supported>true</async-supported> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>ASYNC</dispatcher> </filter-mapping>That’s it! Now Spring Security will ensure that your SecurityContext is propagated on asynchronous requests too.
So how does it work? If you are not really interested, feel free to skip the remainder of this section, otherwise read on. Most of this is built into the Servlet specification, but there is a little bit of tweaking that Spring Security does to ensure things work with asynchronous requests properly. Prior to Spring Security 3.2, the SecurityContext from the SecurityContextHolder was automatically saved as soon as the HttpServletResponse was committed. This can cause issues in an Async environment. For example, consider the following:
httpServletRequest.startAsync(); new Thread("AsyncThread") { @Override public void run() { try { // Do work TimeUnit.SECONDS.sleep(1); // Write to and commit the httpServletResponse httpServletResponse.getOutputStream().flush(); } catch (Exception e) { e.printStackTrace(); }.start();The issue is that this Thread is not known to Spring Security, so the SecurityContext is not propagated to it. This means when we commit the HttpServletResponse there is no SecuriytContext. When Spring Security automatically saved the SecurityContext on committing the HttpServletResponse it would lose our logged in user.
Since version 3.2, Spring Security is smart enough to no longer automatically save the SecurityContext on commiting the HttpServletResponse as soon as HttpServletRequest.startAsync() is invoked.
The following section describes the Servlet 3.1 methods that Spring Security integrates with.
The HttpServletRequest.changeSessionId() is the default method for protecting against Session Fixation attacks in Servlet 3.1 and higher.
Spring Security provides Spring Data integration that allows referring to the current user within your queries. It is not only useful but necessary to include the user in the queries to support paged results since filtering the results afterwards would not scale.
To use this support, add
org.springframework.security:spring-security-data
dependency and provide a bean of typeSecurityEvaluationContextExtension
. In Java Configuration, this would look like:@Bean public SecurityEvaluationContextExtension securityEvaluationContextExtension() { return new SecurityEvaluationContextExtension();In XML Configuration, this would look like:
<bean class="org.springframework.security.data.repository.query.SecurityEvaluationContextExtension"/>Now Spring Security can be used within your queries. For example:
@Repository public interface MessageRepository extends PagingAndSortingRepository<Message,Long> { @Query("select m from Message m where m.to.id = ?#{ principal?.id }") Page<Message> findInbox(Pageable pageable);This checks to see if the
Authentication.getPrincipal().getId()
is equal to the recipient of theMessage
. Note that this example assumes you have customized the principal to be an Object that has an id property. By exposing theSecurityEvaluationContextExtension
bean, all of the Common Security Expressions are available within the Query.In most environments, Security is stored on a per
Thread
basis. This means that when work is done on a newThread
, theSecurityContext
is lost. Spring Security provides some infrastructure to help make this much easier for users. Spring Security provides low level abstractions for working with Spring Security in multi-threaded environments. In fact, this is what Spring Security builds on to integration with the section called “AsyncContext.start(Runnable)” and Section 14.6.4, “Spring MVC Async Integration”.One of the most fundamental building blocks within Spring Security’s concurrency support is the
DelegatingSecurityContextRunnable
. It wraps a delegateRunnable
in order to initialize theSecurityContextHolder
with a specifiedSecurityContext
for the delegate. It then invokes the delegate Runnable ensuring to clear theSecurityContextHolder
afterwards. TheDelegatingSecurityContextRunnable
looks something like this:public void run() { try { SecurityContextHolder.setContext(securityContext); delegate.run(); } finally { SecurityContextHolder.clearContext();While very simple, it makes it seamless to transfer the SecurityContext from one Thread to another. This is important since, in most cases, the SecurityContextHolder acts on a per Thread basis. For example, you might have used Spring Security’s the section called “<global-method-security>” support to secure one of your services. You can now easily transfer the
SecurityContext
of the currentThread
to theThread
that invokes the secured service. An example of how you might do this can be found below:Runnable originalRunnable = new Runnable() { public void run() { // invoke secured service SecurityContext context = SecurityContextHolder.getContext(); DelegatingSecurityContextRunnable wrappedRunnable = new DelegatingSecurityContextRunnable(originalRunnable, context); new Thread(wrappedRunnable).start();The code above performs the following steps:
Creates aRunnable
that will be invoking our secured service. Notice that it is not aware of Spring Security Obtains theSecurityContext
that we wish to use from theSecurityContextHolder
and initializes theDelegatingSecurityContextRunnable
Use theDelegatingSecurityContextRunnable
to create a Thread Start the Thread we createdSince it is quite common to create a
DelegatingSecurityContextRunnable
with theSecurityContext
from theSecurityContextHolder
there is a shortcut constructor for it. The following code is the same as the code above:Runnable originalRunnable = new Runnable() { public void run() { // invoke secured service DelegatingSecurityContextRunnable wrappedRunnable = new DelegatingSecurityContextRunnable(originalRunnable); new Thread(wrappedRunnable).start();The code we have is simple to use, but it still requires knowledge that we are using Spring Security. In the next section we will take a look at how we can utilize
DelegatingSecurityContextExecutor
to hide the fact that we are using Spring Security.In the previous section we found that it was easy to use the
DelegatingSecurityContextRunnable
, but it was not ideal since we had to be aware of Spring Security in order to use it. Let’s take a look at howDelegatingSecurityContextExecutor
can shield our code from any knowledge that we are using Spring Security.The design of
DelegatingSecurityContextExecutor
is very similar to that ofDelegatingSecurityContextRunnable
except it accepts a delegateExecutor
instead of a delegateRunnable
. You can see an example of how it might be used below:SecurityContext context = SecurityContextHolder.createEmptyContext(); Authentication authentication = new UsernamePasswordAuthenticationToken("user","doesnotmatter", AuthorityUtils.createAuthorityList("ROLE_USER")); context.setAuthentication(authentication); SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor(); DelegatingSecurityContextExecutor executor = new DelegatingSecurityContextExecutor(delegateExecutor, context); Runnable originalRunnable = new Runnable() { public void run() { // invoke secured service executor.execute(originalRunnable);The code performs the following steps:
Creates theSecurityContext
to be used for ourDelegatingSecurityContextExecutor
. Note that in this example we simply create theSecurityContext
by hand. However, it does not matter where or how we get theSecurityContext
(i.e. we could obtain it from theSecurityContextHolder
if we wanted). Creates a delegateExecutor that is in charge of executing submittedRunnable
s Finally we create aDelegatingSecurityContextExecutor
which is in charge of wrapping any Runnable that is passed into the execute method with aDelegatingSecurityContextRunnable
. It then passes the wrapped Runnable to the delegateExecutor. In this instance, the sameSecurityContext
will be used for every Runnable submitted to ourDelegatingSecurityContextExecutor
. This is nice if we are running background tasks that need to be run by a user with elevated privileges. At this point you may be asking yourself "How does this shield my code of any knowledge of Spring Security?" Instead of creating theSecurityContext
and theDelegatingSecurityContextExecutor
in our own code, we can inject an already initialized instance ofDelegatingSecurityContextExecutor
.@Autowired private Executor executor; // becomes an instance of our DelegatingSecurityContextExecutor public void submitRunnable() { Runnable originalRunnable = new Runnable() { public void run() { // invoke secured service executor.execute(originalRunnable);Now our code is unaware that the
SecurityContext
is being propagated to theThread
, then theoriginalRunnable
is executed, and then theSecurityContextHolder
is cleared out. In this example, the same user is being used to execute each Thread. What if we wanted to use the user fromSecurityContextHolder
at the time we invokedexecutor.execute(Runnable)
(i.e. the currently logged in user) to processoriginalRunnable
? This can be done by removing theSecurityContext
argument from ourDelegatingSecurityContextExecutor
constructor. For example:SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor(); DelegatingSecurityContextExecutor executor = new DelegatingSecurityContextExecutor(delegateExecutor);Now anytime
executor.execute(Runnable)
is executed theSecurityContext
is first obtained by theSecurityContextHolder
and then thatSecurityContext
is used to create ourDelegatingSecurityContextRunnable
. This means that we are executing ourRunnable
with the same user that was used to invoke theexecutor.execute(Runnable)
code.Refer to the Javadoc for additional integrations with both the Java concurrent APIs and the Spring Task abstractions. They are quite self-explanatory once you understand the previous code.
DelegatingSecurityContextCallable DelegatingSecurityContextExecutor DelegatingSecurityContextExecutorService DelegatingSecurityContextRunnable DelegatingSecurityContextScheduledExecutorService DelegatingSecurityContextSchedulingTaskExecutor DelegatingSecurityContextAsyncTaskExecutor DelegatingSecurityContextTaskExecutor DelegatingSecurityContextTaskSchedulerSpring Security has added Jackson Support for persisting Spring Security related classes. This can improve the performance of serializing Spring Security related classes when working with distributed sessions (i.e. session replication, Spring Session, etc).
To use it, register the
SecurityJackson2Modules.getModules(ClassLoader)
as Jackson Modules.ObjectMapper mapper = new ObjectMapper(); ClassLoader loader = getClass().getClassLoader(); List<Module> modules = SecurityJackson2Modules.getModules(loader); mapper.registerModules(modules); // ... use ObjectMapper as normally ... SecurityContext context = new SecurityContextImpl(); // ... String json = mapper.writeValueAsString(context);Spring Security supports localization of exception messages that end users are likely to see. If your application is designed for English-speaking users, you don’t need to do anything as by default all Security messages are in English. If you need to support other locales, everything you need to know is contained in this section.
All exception messages can be localized, including messages related to authentication failures and access being denied (authorization failures). Exceptions and logging messages that are focused on developers or system deployers (including incorrect attributes, interface contract violations, using incorrect constructors, startup time validation, debug-level logging) are not localized and instead are hard-coded in English within Spring Security’s code.
Shipping in the
spring-security-core-xx.jar
you will find anorg.springframework.security
package that in turn contains amessages.properties
file, as well as localized versions for some common languages. This should be referred to by yourApplicationContext
, as Spring Security classes implement Spring’sMessageSourceAware
interface and expect the message resolver to be dependency injected at application context startup time. Usually all you need to do is register a bean inside your application context to refer to the messages. An example is shown below:<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <property name="basename" value="classpath:org/springframework/security/messages"/> </bean>The
messages.properties
is named in accordance with standard resource bundles and represents the default language supported by Spring Security messages. This default file is in English.If you wish to customize the
messages.properties
file, or support other languages, you should copy the file, rename it accordingly, and register it inside the above bean definition. There are not a large number of message keys inside this file, so localization should not be considered a major initiative. If you do perform localization of this file, please consider sharing your work with the community by logging a JIRA task and attaching your appropriately-named localized version ofmessages.properties
.Spring Security relies on Spring’s localization support in order to actually lookup the appropriate message. In order for this to work, you have to make sure that the locale from the incoming request is stored in Spring’s
org.springframework.context.i18n.LocaleContextHolder
. Spring MVC’sDispatcherServlet
does this for your application automatically, but since Spring Security’s filters are invoked before this, theLocaleContextHolder
needs to be set up to contain the correctLocale
before the filters are called. You can either do this in a filter yourself (which must come before the Spring Security filters inweb.xml
) or you can use Spring’sRequestContextFilter
. Please refer to the Spring Framework documentation for further details on using localization with Spring.The "contacts" sample application is set up to use localized messages.
Spring Security provides a number of optional integrations with Spring MVC. This section covers the integration in further detail.
As of Spring Security 4.0,
@EnableWebMvcSecurity
is deprecated. The replacement is@EnableWebSecurity
which will determine adding the Spring MVC features based upon the classpath.To enable Spring Security integration with Spring MVC add the
@EnableWebSecurity
annotation to your configuration.Spring Security provides the configuration using Spring MVC’s WebMvcConfigurer. This means that if you are using more advanced options, like integrating with
WebMvcConfigurationSupport
directly, then you will need to manually provide the Spring Security configuration.Spring Security provides deep integration with how Spring MVC matches on URLs with
MvcRequestMatcher
. This is helpful to ensure your Security rules match the logic used to handle your requests.In order to use
MvcRequestMatcher
you must place the Spring Security Configuration in the sameApplicationContext
as yourDispatcherServlet
. This is necessary because Spring Security’sMvcRequestMatcher
expects aHandlerMappingIntrospector
bean with the name ofmvcHandlerMappingIntrospector
to be registered by your Spring MVC configuration that is used to perform the matching.For a
web.xml
this means that you should place your configuration in theDispatcherServlet.xml
.<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- All Spring Configuration (both MVC and Security) are in /WEB-INF/spring/ --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/*.xml</param-value> </context-param> <servlet> <servlet-name>spring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- Load from the ContextLoaderListener --> <init-param> <param-name>contextConfigLocation</param-name> <param-value></param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>Below
WebSecurityConfiguration
in placed in theDispatcherServlet
sApplicationContext
.public class SecurityInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return null; @Override protected Class<?>[] getServletConfigClasses() { return new Class[] { RootConfiguration.class, WebMvcConfiguration.class }; @Override protected String[] getServletMappings() { return new String[] { "/" };It is always recommended to provide authorization rules by matching on the
HttpServletRequest
and method security.Providing authorization rules by matching on
HttpServletRequest
is good because it happens very early in the code path and helps reduce the attack surface. Method security ensures that if someone has bypassed the web authorization rules, that your application is still secured. This is what is known as Defence in DepthConsider a controller that is mapped as follows:
@RequestMapping("/admin") public String admin() {If we wanted to restrict access to this controller method to admin users, a developer can provide authorization rules by matching on the
HttpServletRequest
with the following:protected configure(HttpSecurity http) throws Exception { .authorizeRequests(authorizeRequests -> authorizeRequests .antMatchers("/admin").hasRole("ADMIN")or in XML
<intercept-url pattern="/admin" access="hasRole('ADMIN')"/> </http>With either configuration, the URL
/admin
will require the authenticated user to be an admin user. However, depending on our Spring MVC configuration, the URL/admin.html
will also map to ouradmin()
method. Additionally, depending on our Spring MVC configuration, the URL/admin/
will also map to ouradmin()
method.The problem is that our security rule is only protecting
/admin
. We could add additional rules for all the permutations of Spring MVC, but this would be quite verbose and tedious.Instead, we can leverage Spring Security’s
MvcRequestMatcher
. The following configuration will protect the same URLs that Spring MVC will match on by using Spring MVC to match on the URL.protected configure(HttpSecurity http) throws Exception { .authorizeRequests(authorizeRequests -> authorizeRequests .mvcMatchers("/admin").hasRole("ADMIN")or in XML
<http request-matcher="mvc"> <intercept-url pattern="/admin" access="hasRole('ADMIN')"/> </http>Spring Security provides
AuthenticationPrincipalArgumentResolver
which can automatically resolve the currentAuthentication.getPrincipal()
for Spring MVC arguments. By using@EnableWebSecurity
you will automatically have this added to your Spring MVC configuration. If you use XML based configuration, you must add this yourself. For example:<mvc:annotation-driven> <mvc:argument-resolvers> <bean class="org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver" /> </mvc:argument-resolvers> </mvc:annotation-driven>Once
AuthenticationPrincipalArgumentResolver
is properly configured, you can be entirely decoupled from Spring Security in your Spring MVC layer.Consider a situation where a custom
UserDetailsService
that returns anObject
that implementsUserDetails
and your ownCustomUser
Object
. TheCustomUser
of the currently authenticated user could be accessed using the following code:@RequestMapping("/messages/inbox") public ModelAndView findMessagesForUser() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); CustomUser custom = (CustomUser) authentication == null ? null : authentication.getPrincipal(); // .. find messages for this user and return them ...As of Spring Security 3.2 we can resolve the argument more directly by adding an annotation. For example:
import org.springframework.security.core.annotation.AuthenticationPrincipal; // ... @RequestMapping("/messages/inbox") public ModelAndView findMessagesForUser(@AuthenticationPrincipal CustomUser customUser) { // .. find messages for this user and return them ...Sometimes it may be necessary to transform the principal in some way. For example, if
CustomUser
needed to be final it could not be extended. In this situation theUserDetailsService
might returns anObject
that implementsUserDetails
and provides a method namedgetCustomUser
to accessCustomUser
. For example, it might look like:public class CustomUserUserDetails extends User { // ... public CustomUser getCustomUser() { return customUser;We could then access the
CustomUser
using a SpEL expression that usesAuthentication.getPrincipal()
as the root object:import org.springframework.security.core.annotation.AuthenticationPrincipal; // ... @RequestMapping("/messages/inbox") public ModelAndView findMessagesForUser(@AuthenticationPrincipal(expression = "customUser") CustomUser customUser) { // .. find messags for this user and return them ...We can also refer to Beans in our SpEL expressions. For example, the following could be used if we were using JPA to manage our Users and we wanted to modify and save a property on the current user.
import org.springframework.security.core.annotation.AuthenticationPrincipal; // ... @PutMapping("/users/self") public ModelAndView updateName(@AuthenticationPrincipal(expression = "@jpaEntityManager.merge(#this)") CustomUser attachedCustomUser, @RequestParam String firstName) { // change the firstName on an attached instance which will be persisted to the database attachedCustomUser.setFirstName(firstName); // ...We can further remove our dependency on Spring Security by making
@AuthenticationPrincipal
a meta annotation on our own annotation. Below we demonstrate how we could do this on an annotation named@CurrentUser
.It is important to realize that in order to remove the dependency on Spring Security, it is the consuming application that would create
@CurrentUser
. This step is not strictly required, but assists in isolating your dependency to Spring Security to a more central location.@Target({ElementType.PARAMETER, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @AuthenticationPrincipal public @interface CurrentUser {}Now that
@CurrentUser
has been specified, we can use it to signal to resolve ourCustomUser
of the currently authenticated user. We have also isolated our dependency on Spring Security to a single file.@RequestMapping("/messages/inbox") public ModelAndView findMessagesForUser(@CurrentUser CustomUser customUser) { // .. find messages for this user and return them ...Spring Web MVC 3.2+ has excellent support for Asynchronous Request Processing. With no additional configuration, Spring Security will automatically setup the
SecurityContext
to theThread
that executes aCallable
returned by your controllers. For example, the following method will automatically have itsCallable
executed with theSecurityContext
that was available when theCallable
was created:@RequestMapping(method=RequestMethod.POST) public Callable<String> processUpload(final MultipartFile file) { return new Callable<String>() { public Object call() throws Exception { // ... return "someView";
Associating SecurityContext to Callable’s More technically speaking, Spring Security integrates with
WebAsyncManager
. TheSecurityContext
that is used to process theCallable
is theSecurityContext
that exists on theSecurityContextHolder
at the timestartCallableProcessing
is invoked.There is no automatic integration with a
DeferredResult
that is returned by controllers. This is becauseDeferredResult
is processed by the users and thus there is no way of automatically integrating with it. However, you can still use Concurrency Support to provide transparent integration with Spring Security.Spring Security will automatically include the CSRF Token within forms that use the Spring MVC form tag. For example, the following JSP:
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:form="http://www.springframework.org/tags/form" version="2.0"> <jsp:directive.page language="java" contentType="text/html" /> <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> <!-- ... --> <c:url var="logoutUrl" value="/logout"/> <form:form action="${logoutUrl}" method="post"> <input type="submit" value="Log out" /> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> </form:form> <!-- ... --> </html> </jsp:root>Will output HTML that is similar to the following:
<!-- ... --> <form action="/context/logout" method="post"> <input type="submit" value="Log out"/> <input type="hidden" name="_csrf" value="f81d4fae-7dec-11d0-a765-00a0c91e6bf6"/> </form> <!-- ... -->Spring Security provides
CsrfTokenArgumentResolver
which can automatically resolve the currentCsrfToken
for Spring MVC arguments. By using @EnableWebSecurity you will automatically have this added to your Spring MVC configuration. If you use XML based configuraiton, you must add this yourself.Once
CsrfTokenArgumentResolver
is properly configured, you can expose theCsrfToken
to your static HTML based application.@RestController public class CsrfController { @RequestMapping("/csrf") public CsrfToken csrf(CsrfToken token) { return token;It is important to keep the
CsrfToken
a secret from other domains. This means if you are using Cross Origin Sharing (CORS), you should NOT expose theCsrfToken
to any external domains.Spring Security 4 added support for securing Spring’s WebSocket support. This section describes how to use Spring Security’s WebSocket support.
You can find a complete working sample of WebSocket security at https://github.com/spring-projects/spring-session/tree/master/samples/boot/websocket.
Direct JSR-356 Support
Spring Security does not provide direct JSR-356 support because doing so would provide little value. This is because the format is unknown, so there is little Spring can do to secure an unknown format. Additionally, JSR-356 does not provide a way to intercept messages, so security would be rather invasive.
@Configuration public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer { protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) { messages .simpDestMatchers("/user/**").authenticated()This will ensure that:
Any inbound CONNECT message requires a valid CSRF token to enforce Same Origin Policy
The SecurityContextHolder is populated with the user within the simpUser header attribute for any inbound request.
Our messages require the proper authorization. Specifically, any inbound message that starts with "/user/" will require ROLE_USER. Additional details on authorization can be found in Section 14.7.3, “WebSocket Authorization”
Spring Security also provides XML Namespace support for securing WebSockets. A comparable XML based configuration looks like the following:
<websocket-message-broker> <intercept-message pattern="/user/**" access="hasRole('USER')" /> </websocket-message-broker>Any inbound CONNECT message requires a valid CSRF token to enforce Same Origin Policy
The SecurityContextHolder is populated with the user within the simpUser header attribute for any inbound request.
Our messages require the proper authorization. Specifically, any inbound message that starts with "/user/" will require ROLE_USER. Additional details on authorization can be found in Section 14.7.3, “WebSocket Authorization”
@Configuration public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer { @Override protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) { messages .nullDestMatcher().authenticated() .simpSubscribeDestMatchers("/user/queue/errors").permitAll() .simpDestMatchers("/app/**").hasRole("USER") .simpSubscribeDestMatchers("/user/**", "/topic/friends/*").hasRole("USER") .simpTypeMatchers(MESSAGE, SUBSCRIBE).denyAll() .anyMessage().denyAll();This will ensure that:
Any message without a destination (i.e. anything other than Message type of MESSAGE or SUBSCRIBE) will require the user to be authenticated
Anyone can subscribe to /user/queue/errors
Any message that has a destination starting with "/app/" will be require the user to have the role ROLE_USER
Any message that starts with "/user/" or "/topic/friends/" that is of type SUBSCRIBE will require ROLE_USER
Any other message of type MESSAGE or SUBSCRIBE is rejected. Due to 6 we do not need this step, but it illustrates how one can match on specific message types.
Any other Message is rejected. This is a good idea to ensure that you do not miss any messages.
Spring Security also provides XML Namespace support for securing WebSockets. A comparable XML based configuration looks like the following:
<websocket-message-broker> <intercept-message type="CONNECT" access="permitAll" /> <intercept-message type="UNSUBSCRIBE" access="permitAll" /> <intercept-message type="DISCONNECT" access="permitAll" /> <intercept-message pattern="/user/queue/errors" type="SUBSCRIBE" access="permitAll" /> <intercept-message pattern="/app/**" access="hasRole('USER')" /> <intercept-message pattern="/user/**" access="hasRole('USER')" /> <intercept-message pattern="/topic/friends/*" access="hasRole('USER')" /> <intercept-message type="MESSAGE" access="denyAll" /> <intercept-message type="SUBSCRIBE" access="denyAll" /> <intercept-message pattern="/**" access="denyAll" /> </websocket-message-broker>Any message of type CONNECT, UNSUBSCRIBE, or DISCONNECT will require the user to be authenticated
Anyone can subscribe to /user/queue/errors
The system can send notifications MESSAGE to all users through a destination of "/topic/system/notifications" Clients can receive notifications by SUBSCRIBE to the "/topic/system/notifications".While we want clients to be able to SUBSCRIBE to "/topic/system/notifications", we do not want to enable them to send a MESSAGE to that destination. If we allowed sending a MESSAGE to "/topic/system/notifications", then clients could send a message directly to that endpoint and impersonate the system.
In general, it is common for applications to deny any MESSAGE sent to a destination that starts with the broker prefix (i.e. "/topic/" or "/queue/").
It is also is important to understand how destinations are transformed.
Users can send messages to a specific user by sending a message to the destination of "/app/chat". The application sees the message, ensures that the "from" attribute is specified as the current user (we cannot trust the client). The application then sends the message to the recipient usingSimpMessageSendingOperations.convertAndSendToUser("toUser", "/queue/messages", message)
. The message gets turned into the destination of "/queue/user/messages-<sessionid>"With the application above, we want to allow our client to listen to "/user/queue" which is transformed into "/queue/user/messages-<sessionid>". However, we do not want the client to be able to listen to "/queue/*" because that would allow the client to see messages for every user.
In general, it is common for applications to deny any SUBSCRIBE sent to a message that starts with the broker prefix (i.e. "/topic/" or "/queue/"). Of course we may provide exceptions to account for things like
Spring contains a section titled Flow of Messages that describes how messages flow through the system. It is important to note that Spring Security only secures the
clientInboundChannel
. Spring Security does not attempt to secure theclientOutboundChannel
.The most important reason for this is performance. For every message that goes in, there are typically many more that go out. Instead of securing the outbound messages, we encourage securing the subscription to the endpoints.
It is important to emphasize that the browser does not enforce the Same Origin Policy for WebSocket connections. This is an extremely important consideration.
Fortunately, since Spring 4.1.5 Spring’s WebSocket and SockJS support restricts access to the current domain. Spring Security adds an additional layer of protection to provide defence in depth.
By default Spring Security requires the CSRF token in any CONNECT message type. This ensures that only a site that has access to the CSRF token can connect. Since only the Same Origin can access the CSRF token, external domains are not allowed to make a connection.
Typically we need to include the CSRF token in an HTTP header or an HTTP parameter. However, SockJS does not allow for these options. Instead, we must include the token in the Stomp headers
Applications can obtain a CSRF token by accessing the request attribute named _csrf. For example, the following will allow accessing the
CsrfToken
in a JSP:var headerName = "${_csrf.headerName}"; var token = "${_csrf.token}";If you are using static HTML, you can expose the
CsrfToken
on a REST endpoint. For example, the following would expose theCsrfToken
on the URL /csrf@RestController public class CsrfController { @RequestMapping("/csrf") public CsrfToken csrf(CsrfToken token) { return token;The JavaScript can make a REST call to the endpoint and use the response to populate the headerName and the token.
We can now include the token in our Stomp client. For example:
var headers = {}; headers[headerName] = token; stompClient.connect(headers, function(frame) {@Configuration public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer { @Override protected boolean sameOriginDisabled() { return true;SockJS provides fallback transports to support older browsers. When using the fallback options we need to relax a few security constraints to allow SockJS to work with Spring Security.
SockJS may use an transport that leverages an iframe. By default Spring Security will deny the site from being framed to prevent Clickjacking attacks. To allow SockJS frame based transports to work, we need to configure Spring Security to allow the same origin to frame the content.
You can customize X-Frame-Options with the frame-options element. For example, the following will instruct Spring Security to use "X-Frame-Options: SAMEORIGIN" which allows iframes within the same domain:
<!-- ... --> <headers> <frame-options policy="SAMEORIGIN" /> </headers> </http>Similarly, you can customize frame options to use the same origin within Java Configuration using the following:
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { // ... .headers(headers -> headers .frameOptions(frameOptions -> frameOptions .sameOrigin()SockJS uses a POST on the CONNECT messages for any HTTP based transport. Typically we need to include the CSRF token in an HTTP header or an HTTP parameter. However, SockJS does not allow for these options. Instead, we must include the token in the Stomp headers as described in the section called “Adding CSRF to Stomp Headers”.
It also means we need to relax our CSRF protection with the web layer. Specifically, we want to disable CSRF protection for our connect URLs. We do NOT want to disable CSRF protection for every URL. Otherwise our site will be vulnerable to CSRF attacks.
We can easily achieve this by providing a CSRF RequestMatcher. Our Java Configuration makes this extremely easy. For example, if our stomp endpoint is "/chat" we can disable CSRF protection for only URLs that start with "/chat/" using the following configuration:
@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { .csrf(csrf -> // ignore our stomp endpoints since they are protected using Stomp headers .ignoringAntMatchers("/chat/**") .headers(headers -> headers // allow same origin to frame our site to support iframe SockJS .frameOptions(frameOptions -> frameOptions .sameOrigin() .authorizeRequests(authorizeRequests ->If we are using XML based configuration, we can use the [email protected]. For example:
<http ...> <csrf request-matcher-ref="csrfMatcher"/> <headers> <frame-options policy="SAMEORIGIN"/> </headers> </http> <b:bean id="csrfMatcher" class="AndRequestMatcher"> <b:constructor-arg value="#{T(org.springframework.security.web.csrf.CsrfFilter).DEFAULT_CSRF_MATCHER}"/> <b:constructor-arg> <b:bean class="org.springframework.security.web.util.matcher.NegatedRequestMatcher"> <b:bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher"> <b:constructor-arg value="/chat/**"/> </b:bean> </b:bean> </b:constructor-arg> </b:bean>Spring Framework provides first class support for CORS. CORS must be processed before Spring Security because the pre-flight request will not contain any cookies (i.e. the
JSESSIONID
). If the request does not contain any cookies and Spring Security is first, the request will determine the user is not authenticated (since there are no cookies in the request) and reject it.The easiest way to ensure that CORS is handled first is to use the
CorsFilter
. Users can integrate theCorsFilter
with Spring Security by providing aCorsConfigurationSource
using the following:@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { // by default uses a Bean by the name of corsConfigurationSource .cors(withDefaults()) @Bean CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList("https://example.com")); configuration.setAllowedMethods(Arrays.asList("GET","POST")); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source;or in XML
<cors configuration-source-ref="corsSource"/> </http> <b:bean id="corsSource" class="org.springframework.web.cors.UrlBasedCorsConfigurationSource"> </b:bean>If you are using Spring MVC’s CORS support, you can omit specifying the
CorsConfigurationSource
and Spring Security will leverage the CORS configuration provided to Spring MVC.@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { // if Spring MVC is on classpath and no CorsConfigurationSource is provided, // Spring Security will use CORS configuration provided to Spring MVC .cors(withDefaults())or in XML
<!-- Default to Spring MVC's CORS configuration --> <cors /> </http>To use any of the tags, you must have the security taglib declared in your JSP:
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>This tag is used to determine whether its contents should be evaluated or not. In Spring Security 3.0, it can be used in two ways [11]. The first approach uses a web-security expression, specified in the
access
attribute of the tag. The expression evaluation will be delegated to theSecurityExpressionHandler<FilterInvocation>
defined in the application context (you should have web expressions enabled in your<http>
namespace configuration to make sure this service is available). So, for example, you might have<sec:authorize access="hasRole('supervisor')"> This content will only be visible to users who have the "supervisor" authority in their list of <tt>GrantedAuthority</tt>s. </sec:authorize>When used in conjuction with Spring Security’s PermissionEvaluator, the tag can also be used to check permissions. For example:
<sec:authorize access="hasPermission(#domain,'read') or hasPermission(#domain,'write')"> This content will only be visible to users who have read or write permission to the Object found as a request attribute named "domain". </sec:authorize>A common requirement is to only show a particular link, if the user is actually allowed to click it. How can we determine in advance whether something will be allowed? This tag can also operate in an alternative mode which allows you to define a particular URL as an attribute. If the user is allowed to invoke that URL, then the tag body will be evaluated, otherwise it will be skipped. So you might have something like
<sec:authorize url="/admin"> This content will only be visible to users who are authorized to send requests to the "/admin" URL. </sec:authorize>To use this tag there must also be an instance of
WebInvocationPrivilegeEvaluator
in your application context. If you are using the namespace, one will automatically be registered. This is an instance ofDefaultWebInvocationPrivilegeEvaluator
, which creates a dummy web request for the supplied URL and invokes the security interceptor to see whether the request would succeed or fail. This allows you to delegate to the access-control setup you defined usingintercept-url
declarations within the<http>
namespace configuration and saves having to duplicate the information (such as the required roles) within your JSPs. This approach can also be combined with amethod
attribute, supplying the HTTP method, for a more specific match.The Boolean result of evaluating the tag (whether it grants or denies access) can be stored in a page context scope variable by setting the
var
attribute to the variable name, avoiding the need for duplicating and re-evaluating the condition at other points in the page.
Caution In general this tag should be considered deprecated. Instead use the Section 14.9.2, “The authorize Tag”.
<sec:accesscontrollist hasPermission="1,2" domainObject="${someObject}"> This will be shown if the user has all of the permissions represented by the values "1" or "2" on the given object. </sec:accesscontrollist>The permissions are passed to the
PermissionFactory
defined in the application context, converting them to ACLPermission
instances, so they may be any format which is supported by the factory - they don’t have to be integers, they could be strings likeREAD
orWRITE
. If noPermissionFactory
is found, an instance ofDefaultPermissionFactory
will be used. TheAclService
from the application context will be used to load theAcl
instance for the supplied object. TheAcl
will be invoked with the required permissions to check if all of them are granted.This tag also supports the
var
attribute, in the same way as theauthorize
tag.<form method="post" action="/do/something"> <sec:csrfInput /> Name:<br /> <input type="text" name="name" /> </form>
发怒的桔子 · 云南省纪委省监委网站 2 月前 |
温文尔雅的杨桃 · 3D光学传感器行业研究宝典 - 知乎 1 年前 |
有情有义的牛肉面 · 只好替女主嫁给未来皇帝了-第12话 有人下毒 1 年前 |