* The preview only shows a few pages of manuals at random. You can get the complete content by filling out the form below.
The preview is currently being created... Please pause for a moment!
Description
Pro Spring Security Securing Spring Framework 5 and Boot 2-based Java Applications — Second Edition — Carlo Scarioni Massimo Nardone
Pro Spring Security Securing Spring Framework 5 and Boot 2-based Java Applications Second Edition
Carlo Scarioni Massimo Nardone
Pro Spring Security: Securing Spring Framework 5 and Boot 2-based Java Applications Carlo Scarioni Surbiton, UK ISBN-13 (pbk): 978-1-4842-5051-8 https://doi.org/10.1007/978-1-4842-5052-5
Massimo Nardone HELSINKI, Finland ISBN-13 (electronic): 978-1-4842-5052-5
I would like to dedicate this book to the memory of my beloved late mother, Maria Augusta Ciniglio. Thanks, Mom, for all the great things you have taught me, for making me a good person, for making me study to become a computing scientist, and for the great memories you left me. You will be loved and missed forever. I love you, Mom. RIP. —Massimo
Table of Contents About the Authors���������������������������������������������������������������������������������������������������� ix About the Technical Reviewer��������������������������������������������������������������������������������� xi Acknowledgments������������������������������������������������������������������������������������������������� xiii Introduction�������������������������������������������������������������������������������������������������������������xv Chapter 1: The Scope of Security����������������������������������������������������������������������������� 1 The Network Security Layer���������������������������������������������������������������������������������������������������������� 2 The Operating System Layer��������������������������������������������������������������������������������������������������������� 3 The Application Layer�������������������������������������������������������������������������������������������������������������������� 3 Authentication������������������������������������������������������������������������������������������������������������������������� 4 Authorization��������������������������������������������������������������������������������������������������������������������������� 5 ACLs���������������������������������������������������������������������������������������������������������������������������������������� 6 Authentication and Authorization: General Concepts�������������������������������������������������������������������� 6 What to Secure��������������������������������������������������������������������������������������������������������������������������� 10 More Security Concerns�������������������������������������������������������������������������������������������������������������� 11 Java Options for Security������������������������������������������������������������������������������������������������������������ 12 Summary������������������������������������������������������������������������������������������������������������������������������������ 14
Chapter 2: Introducing Spring Security������������������������������������������������������������������ 15 What Is Spring Security?������������������������������������������������������������������������������������������������������������� 15 Where Does Spring Security Fit In?�������������������������������������������������������������������������������������������� 17 Spring Security Overview����������������������������������������������������������������������������������������������������������� 20 Spring Framework 5: A Quick Overview�������������������������������������������������������������������������������������� 23 Dependency Injection������������������������������������������������������������������������������������������������������������ 25 Aspect-Oriented Programming���������������������������������������������������������������������������������������������� 26
v
Table of Contents
What’s New in Spring Security 5?����������������������������������������������������������������������������������������������� 28 Summary������������������������������������������������������������������������������������������������������������������������������������ 34
Chapter 3: Setting Up the Scene����������������������������������������������������������������������������� 35 Setting Up the Development Environment���������������������������������������������������������������������������������� 35 Creating a New Java Web Application Project���������������������������������������������������������������������������� 42 Creating a New Spring Security v5 Project��������������������������������������������������������������������������������� 46 Spring Security v5 Source����������������������������������������������������������������������������������������������������� 46 Spring Security Taglibs��������������������������������������������������������������������������������������������������������������� 50 Introducing Cross-Site Request Forgery������������������������������������������������������������������������������������� 51 Adding Spring Security v5 (and Spring Core Itself) to the Project���������������������������������������������� 51 Configuring the Web Project to Be Aware of Spring Security v5������������������������������������������������� 56 Summary������������������������������������������������������������������������������������������������������������������������������������ 68
Chapter 4: Spring Security Architecture and Design���������������������������������������������� 69 What Components Make Up Spring Security?���������������������������������������������������������������������������� 69 The 10,000-Foot View������������������������������������������������������������������������������������������������������������ 69 The 1,000-Foot View�������������������������������������������������������������������������������������������������������������� 70 The 100-Foot View����������������������������������������������������������������������������������������������������������������� 71 Good Design and Patterns in Spring Security��������������������������������������������������������������������������� 113 Strategy Pattern������������������������������������������������������������������������������������������������������������������ 114 Decorator Pattern���������������������������������������������������������������������������������������������������������������� 114 SRP�������������������������������������������������������������������������������������������������������������������������������������� 115 DI����������������������������������������������������������������������������������������������������������������������������������������� 115 Summary���������������������������������������������������������������������������������������������������������������������������������� 115
Chapter 5: Web Security��������������������������������������������������������������������������������������� 117 Introducing the Simple Example Application���������������������������������������������������������������������������� 117 The Special URLs���������������������������������������������������������������������������������������������������������������������� 135 Custom Login Form������������������������������������������������������������������������������������������������������������� 135 Basic HTTP Authentication�������������������������������������������������������������������������������������������������� 143 Digest Authentication���������������������������������������������������������������������������������������������������������� 145
vi
Table of Contents
Remember-Me Authentication��������������������������������������������������������������������������������������������� 148 Logging Out������������������������������������������������������������������������������������������������������������������������� 151 Session Management���������������������������������������������������������������������������������������������������������� 154 Forcing the Request to HTTPS��������������������������������������������������������������������������������������������� 155 Summary���������������������������������������������������������������������������������������������������������������������������������� 157
Chapter 6: Configuring Alternative Authentication Providers������������������������������ 159 Database-Provided Authentication�������������������������������������������������������������������������������������������� 160 Creating the Basic Tables���������������������������������������������������������������������������������������������������� 173 Using Groups����������������������������������������������������������������������������������������������������������������������� 175 Using Existing Schemas������������������������������������������������������������������������������������������������������ 177 LDAP Authentication������������������������������������������������������������������������������������������������������������������ 179 Installing and Configuring LDAP������������������������������������������������������������������������������������������ 180 X.509 Authentication����������������������������������������������������������������������������������������������������������������� 199 OAuth 2������������������������������������������������������������������������������������������������������������������������������������� 215 JSON Web Token����������������������������������������������������������������������������������������������������������������������� 216 Spring WebSockets������������������������������������������������������������������������������������������������������������������� 217 JAAS Authentication������������������������������������������������������������������������������������������������������������������ 218 Central Authentication Service Authentication�������������������������������������������������������������������������� 218 Overview������������������������������������������������������������������������������������������������������������������������������ 218 Summary���������������������������������������������������������������������������������������������������������������������������������� 219
Chapter 7: Business Object Security with ACLs��������������������������������������������������� 221 The Security Example Application��������������������������������������������������������������������������������������������� 221 Accessing Secured Objects������������������������������������������������������������������������������������������������������� 256 Filtering Returned Objects�������������������������������������������������������������������������������������������������������� 263 Test Scenario 7-7���������������������������������������������������������������������������������������������������������������� 268 Securing the View Layer with ACLs������������������������������������������������������������������������������������������� 273 The Cost of ACLs����������������������������������������������������������������������������������������������������������������������� 275 Summary���������������������������������������������������������������������������������������������������������������������������������� 277
vii
Table of Contents
Chapter 8: Customizing and Extending Spring Security��������������������������������������� 279 Spring Security Extension Points���������������������������������������������������������������������������������������������� 279 Plugging into the Spring Security Event System����������������������������������������������������������������������� 280 Authorization-Related Events���������������������������������������������������������������������������������������������� 282 Authentication-Related Events�������������������������������������������������������������������������������������������� 284 Session-Related Events������������������������������������������������������������������������������������������������������� 288 Your Own AuthenticationProvider and UserDetailsService�������������������������������������������������� 290 Password Encryption���������������������������������������������������������������������������������������������������������������� 332 Handling Errors and Entry Points����������������������������������������������������������������������������������������� 334 Changing the Security Interceptor��������������������������������������������������������������������������������������� 340 Spring Security Extensions Project������������������������������������������������������������������������������������� 344 Summary���������������������������������������������������������������������������������������������������������������������������������� 344
Chapter 9: Integrating Spring Security with Other Frameworks and Languages������������������������������������������������������������������������������������������������������������ 345 Spring Security with Struts 2���������������������������������������������������������������������������������������������������� 346 Spring Security with Spring Web Flow�������������������������������������������������������������������������������������� 357 Spring Security in Other JVM Languages��������������������������������������������������������������������������������� 371 Spring Security and Ruby (JRuby)�������������������������������������������������������������������������������������������� 372 Web-Layer Security in Rails������������������������������������������������������������������������������������������������������ 374 Spring Security, Groovy, and Grails������������������������������������������������������������������������������������������� 381 Using Grails to Secure the Web Layer with URL Rules������������������������������������������������������������� 382 Using Grails Security at the Method Level�������������������������������������������������������������������������������� 386 Spring Security and Scala��������������������������������������������������������������������������������������������������������� 387 Summary���������������������������������������������������������������������������������������������������������������������������������� 399
About the Authors Carlo Scarioni is a passionate software developer, motivated by learning and applying innovative and interesting software development tools, techniques, and methodologies. His professional objectives are the following: to be in a technology-oriented enterprise where the technical staff is the soul of the company; to be on an important IT team; to be able to design and develop state-of-the-art software; to be able to apply new knowledge every day, in innovative ways, and with a great degree of freedom; to architect, design, and develop software that uses the best practices of the field; and to play with the latest technologies, learn every day, and participate in the research and innovation of software products. His specialties are TDD, object-oriented principles and design patterns, Java/JEE, Spring, application servers, SQL and NoSQL (MongoDB), multithreading, messaging, Enterprise Integration Patterns, Ruby, and RoR. His certifications are Sun Certified Enterprise Architect (Part I), Sun Certified Java Programmer, Sun Certified Business Component Developer, SpringSource Certified Professional, and IBM SOA Certified Associate. Massimo Nardone has more than 24 years of experience in security, web/mobile development, cloud, and IT architecture. His true IT passions are security and Android. He has been programming and teaching how to program with Android, Perl, PHP, Java, VB, Python, C/C++, and MySQL for more than 20 years. He holds a Master of Science degree in Computing Science from the University of Salerno, Italy. His technical skills include security, Android, cloud, Java, MySQL, Drupal, Cobol, Perl, web and mobile development, MongoDB, Couchbase, C/C++, WebGL, Python, Pro Rails, Django CMS, Jekyll, Scratch, etc.
ix
About the Authors
He has worked as a visiting lecturer and supervisor for exercises at the Networking Laboratory of the Helsinki University of Technology (Aalto University). He holds four international patents (in the PKI, SIP, SAML, and Proxy areas). He currently works as an Executive Security Consultant, OT Security Leader, and Chief Information Security Office (CISO) for IBM, and he is member of ISACA Finland Chapter Board. Massimo is the coauthor of Pro JPA in Java EE 8 (Apress, 2018), Beginning EJB in Java EE 8 (Apress, 2018), and Pro Android Games (Apress, 2015); he also reviewed more than 45 IT books for different publishers.
x
About the Technical Reviewer Iuliana Cosmina is a Spring-certified Web Application Developer and a Spring-certified Spring Professional, as defined by Pivotal, the makers of Spring Framework, Boot, and other tools. She is the author of books with Apress on core Spring certification and Spring-certified web development. She is a software architect at Bearing Point Software and is an active coder and software contributor on GitHub, Stack Overflow, and more.
xi
Acknowledgments This book is definitely the work of more than one person. The people involved in the preparation of this book have brought so much experience and quality to the final version that the end product is many times better than if I had done all the work myself. Their input ranged from improving text style, to introducing better ways to present concepts, to performing code reviews and suggesting general improvements that have made this book a much better reading experience. I am talking, of course, about the great people at Apress who have been with me along the full journey of writing this book. I’m talking about Steve Anglin, who initiated me into the project, kept an eye from afar on the progress of the book, and tried to make sure I kept on track as much as possible. I’m talking about Kevin Shea, who was my main editorial contact and made sure that I stayed on schedule with the book and helped with advice and support. I’m talking about Tom Welsh, who had the great responsibility of reading every chapter as I was writing it; he gave me great input on each section, including helping with my use of English grammar as well as ways to make the different parts more attractive to potential readers. I am talking about Manuel Jordan, who not only read every single chapter in a very detailed way, but also took on the laborious job of evaluating and executing every single line of code and making sure that the book provides code samples that can be reproduced by the readers in their own environments. His input is greatly appreciated, and it is the difference between having a full book or a half a book. There were, of course, many more people at Apress involved in the full review phases of the book, and I want to say “thank you for your help” to all of them. I would like to also thank the creators, committers, and community of Spring and Spring Security for creating such an amazing piece of software and making it available to everyone. A big thank you to them for letting all developers share their knowledge and ways of work by freely distributing the source code of the different projects covered by the SpringSource umbrella. They make us all wiser and better developers. Finally, I want to thank my wife for being with me all the time and motivating me to keep going forward. —Carlo Scarioni
xiii
Acknowledgments
Many thanks go to my wonderful family—my wife, Pia, and my children, Luna, Leo, and Neve—for supporting me while I was working on this book. You are the most beautiful reason of my life. I want to thank my beloved late mother, Maria Augusta Ciniglio, who always supported and loved me so much. I will love and miss you forever, my dearest mom. Thanks to my beloved father, Giuseppe, and my brothers, Mario and Roberto, for your endless love and for being the best dad and brothers in the world. I also want to dedicate this book to my late, dearest cousin, Gerardo “Amerigo” Nardone. You will be missed. Thanks a lot to Steve Anglin and Matthew Moodie for giving me the opportunity to work as writer on this book, as well as to Mark Powers for doing such a great job during the editorial process and supporting me all the time, and of course the technical reviewers of this book for helping me to make a better book. —Massimo Nardone
xiv
Introduction Denying the impact of the Spring Framework in the Java world would be simply impossible. Spring has brought so many advantages to the Java developer that we could say it has made better developers of all of us. The good ones, the average ones. All of us. The previous version of this book utilized Spring Security 3. Therefore, it is very important to note, in this new edition of the book, the most important changes from v3 to v5. Spring Security v5 is now part of Pivotal since SpringSource is no longer used. Spring Framework 5 was published in September of 2017 and it can be considered the first major Spring Framework release since version 4 was released in December of 2013. Spring’s core building blocks of dependency injection and aspect-oriented programming are widely applicable to many business and infrastructure concerns, and certainly application security can benefit from these core functionalities. So this is Spring Security: an application-level security framework built on top of the powerful Spring Framework that deals mainly with the core security concepts of authentication and authorization, which are some of the fundamental functionalities of Spring Security v5. Spring Security aims to be a full-featured security solution for your Java applications. Although its main focus is on web applications and the Java programming language, you will see that it goes beyond these two domains. What we wanted to do in writing this book was to expose some of the internal works of Spring Security along with the standard explanations of how to use certain features. The idea is to teach beyond the basics of how to do something in particular, and instead focus on the plumbing inside the framework. We find that this is the best way of learning something: actually seeing how it is built in the core. That’s not to say, of course, that the book doesn’t cover basic setups and give quick, practical advice on using the framework, because it certainly does. The point is that instead of saying, “Use this to do that,” we say, “This works like this… and this allows you to….” This is a point of view that only tools like Spring afford (because they are open source). With that said, we suggest that the best way to use this book is to have the Spring Security source code checked out on your computer and go through the examples with both the code from the book and the code from Spring Security itself. This will not only help you understand each concept as it is introduced, but will also teach more than xv
Introduction
one good programming trick and good practice. We recommend this approach for studying any software whenever you have the chance. If the source code is out there, grab it. Sometimes a couple lines of code teach more than a thousand words. In this book, we will mainly introduce Spring Boot, analyze Spring Framework, and develop Java Web Applications with Spring Security v5.1.5, Java v11, and Servlet v4. Also, Spring Security v5 supports many different authentication mechanisms which are introduced and developed in this book, like Database (MongoDB and hsqldb), LDAP, X.509, OAuth 2/OpenID, WebSockets, SON Web Token (JWT), JAAS, and CAS. Web development frameworks like Grails and JRuby in the context of Rails and Scala are also introduced in this book.
Who This Book Is For This book is written mainly for Java developers who use Spring in their work and need to add security to their applications in a way that leverages Spring’s proven concepts and techniques. The book will also be helpful to developers who want to add web-layer security to their applications, even if those applications are not fully Spring-powered at their core. The book assumes you have knowledge of Java and some of its tools and libraries, such as Servlets and Maven. It also assumes that you know what you want to use security for and in what context you want to use it. This means, for example, we won’t explain protocols like LDAP in much depth; instead, we’ll concentrate on showing you how to integrate Spring Security with an LDAP user store. An in-depth knowledge of Spring is not essential because many of the concepts are introduced as we go along, but the more you understand about Spring, the more you are likely to get out of this book.
How This Book Is Structured The book is divided into nine chapters that embody a progressive study of Spring Security. Starting from a summary of basic applications and an explanation of how the framework is structured, the content moves on to more advanced topics, such as using Spring Security in different JVM languages. The book follows a sequence that corresponds to the way this framework is normally used in real life.
xvi
Introduction
The chapters in the book cover the following: •
Chapter 1: Introduces security in general and how to approach security problems at the application level
•
Chapter 2: Introduces Spring Security v5, how to use it, when to use it, and all of its security functionalities
•
Chapter 3: Introduces Spring Security with a simple example application that secures web access at the URL level
•
Chapter 4: Provides a full introduction to the architecture of Spring Security, including the main components and how they interact with each other
•
Chapter 5: Gives in-depth coverage of the web-layer security options available in Spring Security
•
Chapter 6: Covers a wide array of authentication providers, including LDAP and JASS, which can be plugged into Spring Security
•
Chapter 7: Covers access control lists (ACLs), which are used to secure individual domain objects, and how they fit into the general security concerns
•
Chapter 8: Explains how to extend the core Spring Security functionality by making use of the many extension points supported by its modular architecture
•
Chapter 9: Shows how to integrate Spring Security with different Java frameworks and some important JVM programming languages
P rerequisites The examples in this book are all built with Java 11 and Maven 3.6.1. The latest Spring versions are used if possible. Spring Security 5.1.5 was the version used throughout the book. Tomcat Web Server v9 was used for the different web applications in the book, mainly through its Maven plugin, and the laptop used was a ThinkPad Yoga 360 with 8GB of RAM. All the projects were developed using the IntelliJ IDEA Ultimate 2019.2. You are free to use your own tools and operating system. Because everything is Java based, you should be able to compile your programs on any platform without problems. xvii
Introduction
D ownloading the Code The code for the examples shown in this book is available via the Download Source Code button located at www.apress.com/9781484250518.
C ontacting the Authors You are more than welcome to send us any feedback regarding this book or any other subject we might help you with. You can contact Carlo Scarioni via his blog at http:// cscarioni.blogspot.com, or you can send him email at [email protected]. You can contact Massimo Nardone via email at [email protected].
xviii
CHAPTER 1
The Scope of Security Security. An incredibly overloaded word in the IT world. It means so many different things in so many different contexts, but in the end, it is all about protecting sensitive and valuable resources against malicious usage. In IT, we have many layers of infrastructure and code that can be subject to malicious attacks, and arguably we should ensure that all these layers get the appropriate levels of protection. Of course, the growth of the Internet and the pursuit of reaching more people with our applications have opened more and more doors to cyber criminals trying to access these applications in illegitimate ways. It is also true that good care is not always taken to ensure that a properly secured set of services is being offered to the public. And sometimes, even when good care is taken, some hackers are still smart enough to overcome security barriers that, superficially, appear adequate. The first step is to define defense in depth (DiD) and its security layers. In general, DiD is a way to define how to develop the cybersecurity of the IT infrastructure by defining how all the defensive mechanisms are layered in order to protect and secure data and information. A failing DiD or too weak development might be a consequence of a cybersecurity attack on the IT infrastructure. Let’s understand a bit more about the mechanisms part of DiD. First of all, DiD is made of three major controls: •
Administrative controls: Policies, procedures, guidelines, awareness programs, etc.
•
Technical controls: Firewalls, antivirus, intrusion prevention systems (IPS), etc.
•
Physical Controls: Network and server rooms, video surveillance, etc.
Figure 1-1 shows the typical DiD mechanisms that define the IT infrastructure security layers.
Figure 1-1. DiD mechanisms and IT infrastruxture layers The three major security layers in an IT infrastructure are the network, the operating system (part of the endpoint security layer), and the application itself.
The Network Security Layer The network security layer is probably the most familiar one in the IT world. When people talk about IT security, they normally think of network-level security—in particular, security that uses firewalls. Even though people often associate security with the network level, this is only a very limited layer of protection against attackers. Generally speaking, it can do no more than defend IP addresses and filter network packets addressed to certain ports in certain machines in the network.
2
Chapter 1
The Scope of Security
This is clearly not enough in the vast majority of cases, as traffic at this level is normally allowed to enter the publicly open ports of your various exposed services with no restriction at all. Different attacks can be targeted at these open services, as attackers can execute arbitrary commands that could compromise your security constraints. There are tools like the popular nmap (http://nmap.org/) that can be used to scan a machine to find open ports. The use of such tools is an easy first step to take in preparing an attack, because well-known attacks can be used against such open ports if they are not properly secured. A very important part of the network-layer security, in the case of web applications, is the use of Secure Sockets Layer (SSL) to encode all sensitive information sent along the wire, but this is related more to the network protocol at the application level than to the network physical level at which firewalls operate.
The Operating System Layer The operating system layer is probably the most important one in the whole security schema, as a properly secured operating system (OS) environment can at least prevent a whole host machine from going down if a particular application is compromised. If an attacker is somehow allowed to have unsecured access to the operating system, they can basically do whatever they want—from spreading viruses to stealing passwords or deleting your whole server’s data and making it unusable. Even worse perhaps, they could take control of your computer without you even noticing, and use it to perform other malicious acts as part of a botnet. This layer can include the deployment model of the applications since you need to know your operating system’s permission scheme to ensure that you don’t give your applications unnecessary privileges over your machine. Applications should run as isolated as possible from the other components of the host machine.
The Application Layer The main focus of this book will be on the application layer. The application security layer refers to all the constraints we establish in our applications to make sure that only the right people can do only the right things when working through the application.
3
Chapter 1
The Scope of Security
Applications, by default, are open to countless avenues of attack. An improperly secured application can allow an attacker to steal information from the application, impersonate other users, execute restricted operations, corrupt data, gain access to the operating system level, and perform many other malicious acts. In this book, we will cover application-level security, which is the domain of Spring Security. Application-level security is achieved by implementing several techniques, and there are a few concepts that will help you understand better what the rest of the book will cover. They are the main concerns that Spring Security addresses to provide your applications with comprehensive protection against threats. In the following three subsections, we shall introduce •
Authentication
•
Authorization
•
ACLs
A uthentication The process of authentication allows an application to validate that a particular user is who they claim they are. In the authentication process, a user presents the application with information about herself (normally, a username and a password) that no one else knows. The application takes this information and tries to match it against information it has stored—normally, in a database or LDAP1 (Lightweight Directory Access Protocol) server. If the information provided by the user matches a record in the authentication server, the user is successfully authenticated in the system. The application will normally create an internal abstraction representing this authenticated user in the system. Figure 1-2 shows the authentication mechanism.
DAP will be explained in some detail in Chapter 8, where various authentication providers are L covered.
1
4
Chapter 1
The Scope of Security
Figure 1-2. Simple, standard authentication mechanism
Authorization When a user is authenticated, that only means that the user is known to the system and has been recognized by it. It doesn’t mean that the user is free to do whatever she wants in said system. The next logical step in securing an application is to determine which actions the user is allowed to perform, and which resources she has access to, and make sure that if the user doesn’t have the proper permissions she cannot carry out that particular action. This is the work of the authorization process. In the most common case, the authorization process compares the user’s set of permissions against the permissions required to execute a particular action in the application, and if a match is found, access is granted. On the other hand, if no match is found, access is denied. Figure 1-3 shows the authorization mechanism.
5
Chapter 1
The Scope of Security
Figure 1-3. Simple authorization process. The authenticated user tries to access a secured resource
ACLs Access control lists (ACLs) are part of the authorization process explained in the previous section. The key difference is that ACLs normally work at a finer grained level in the application. ACLs are simply a collection of mappings between resources, users, and permissions. With ACLs, you can establish rules like “User John has administrative permission on the blog post X” or “User Luis has read permission on blog post X.” You can see the three elements: user, permission, and resource. Figure 1-3 shows how ACLs work; they are just a special case of the general authorization process.
Authentication and Authorization: General Concepts In this section, we shall introduce and explain some fundamental security concepts that you will be coming across frequently in the rest of the book: •
6
User: The first step in securing a system from malicious attackers is to identify legitimate users and allow access to them alone. User abstractions are created in the system and given their own identity. They are the users that will later be allowed to use the system.
Chapter 1
The Scope of Security
•
Credentials: Credentials are the way a user proves who they are. Normally, in the shape of passwords (certificates are also a common way of presenting credentials), they are data that only the owner of it knows.
•
Role: In an application security context, a role can be seen as a logical grouping of users. This logical grouping is normally done so the grouped users share a set of permissions in the application to access certain resources. For example, all users with the role of admin will have the same access and permissions to the same resources. Roles serve simply as a way to group permissions to execute determined actions, making users with those roles inherit such permissions.
•
Resource: By a resource, we mean, in this context, any part of the application that we want to access and that needs to be properly secured against unauthorized access—for example, a URL, a business method, or a particular business object.
•
Permissions: Permissions refer to the access level needed to access a particular resource. For example, two users may be allowed to read a particular document, but only one of them is allowed to write to it. Permissions can apply either to individual users or to users that share a particular role.
•
Encryption: This allows you to encrypt sensible information (normally passwords, but it can be something else, like cookies) so as to make it incomprehensible to attackers even if they get access to the encrypted version. The idea is that you never store the plain text version of a password, but instead store an encrypted version so that nobody but the owner of such a password knows the original one. There are three main kinds of encryption algorithms: •
One-way encryption: These algorithms, referred as hashing algorithms, take an input string and generate an output number known as the message digest. This output number cannot be converted back into the original string. This is why the technique is referred to as one-way encryption. Here is the way to use it: A requesting client encrypts a string and sends the encrypted string to the server. The server may have access to the original 7
Chapter 1
The Scope of Security
information from a previous registration process, for example, and if it does, it can apply the same hash function to it. Then it compares the output from this hashing to the value sent by the client. If they match, the server validates the information. Figure 1-4 shows this scheme. Usually, the server doesn’t even need the original data. It can simply store the hashed version and then compare it with the incoming hash from the client.
Figure 1-4. One-way encryption or hashing •
8
Symmetric encryption: These algorithms provide two functions: encrypt and decrypt. A string of text is converted into an encrypted form and then can be converted back to the original string. In this scheme, a sender and a receiver share the same keys so that they can encrypt and decrypt messages on both ends of the communication.
Chapter 1
The Scope of Security
One problem with this scheme is how to share the key between the endpoints of the communication. A common approach is to use a parallel secure channel to send the keys. Figure 1-5 shows symmetric encryption at work.
Figure 1-5. Symmetric encryption. The two endpoints share the same encryption/ decryption key •
Public key cryptography: These techniques are based on asymmetric cryptography. In this scheme, a different key is used for encryption than for decryption. These two keys are referred as the public key, which is used to encrypt messages, and the private key, which is used to decrypt messages. The advantage of this approach over symmetric encryption is that there is no need to share the decryption key, so no one but the intended receiver of the information is able to decrypt the message. So the normal scenario is the following: •
The intended recipient of messages shares her public key with everyone interested in sending information to her.
•
A sender encrypts the information with the receiver’s public key, and sends a message.
•
The receiver uses her private key to decrypt the message.
•
No one else is able to decrypt the message because they don’t have the receiver’s private key. 9
Chapter 1
The Scope of Security
Figure 1-6 shows the public key cryptography scheme.
Figure 1-6. Public key cryptography The use of encryption achieves, among other things, two other security goals: •
Confidentiality: Potentially sensitive information belonging to one user, or group of users, should be accessible only to this user or group. Encryption algorithms are the main helper in achieving this goal.
•
Integrity: Data sent by a valid user shouldn’t be altered by a third entity on its way to the server, or in its storage. This is normally accomplished through the use of one-way cryptographic algorithms that make it almost impossible to alter an input and produce a corrupted message whose encrypted hash is the same as the original message (thus deceiving the receiver into thinking it is valid).
What to Secure Not every part of the application requires a strong security model, or even any security at all. If, for example, one part of your application is supposed to serve static content to everyone interested in it, you can simply serve this content. There probably are no security concerns to handle here.
10
Chapter 1
The Scope of Security
Anyway, when starting to work on a new application, you should think about the security constraints that your application will have. You should think about concerns like those in the following list and whether or not they apply to your particular use case: •
Identity management: More than likely, your application will need to establish the identities of the different users that will be using it. Usually, your application will do different things for different users, so you need a way to associate users with certain functionality. You also need to be sure to protect each user’s identity information so that it can’t be compromised.
•
Secured connections: In an internet environment, where anyone in the world can potentially access your system and eavesdrop on other users accessing your system, you most likely will want to secure the communication of sensitive data using some kind of transport layer security, such as SSL.
•
Sensitive data protection: Sensitive data will need to be protected against malicious attacks. This applies to the communication layer and to individual message transmission, as well as to credentials datastores. Encryption should be used in different layers to achieve the most secure application possible.
More Security Concerns There are many more security concerns than the ones explained so far. Because this is a Spring Security book and not a general application-security book, we will cover only things related to Spring Security. However, we think it is important that you understand that there are many more security concerns than those addressed directly by Spring Security. The following is a quick overview of some of the most common ones. This is only intended to make you aware of their existence, and we recommend you consult a different source (such as a general software security textbook) to gain a better understanding of all these concerns.
11
Chapter 1
The Scope of Security
•
SQL (and other code) injection: Validating user input is a very important part of application security. If data is not validated, an attacker could potentially write any kind of string as input (including SQL or server-side code) and send that information to the server. If the server code is not properly written, the attacker could wreak significant havoc because she could execute any arbitrary code on the server.
•
Denial of service attacks: These attacks consist of making the target system unresponsive to its intended users. This is normally done by saturating the server with requests so that it utilizes all the server’s resources and makes it unresponsive to legitimate requests.
•
Cross-site scripting and output sanitation: A kind of injection can be done where the target is the client part of the application. The idea is that the attacker can make an application return malicious code inside the web pages returned, and thus execute it in the user’s browser. This way, the attacker invisibly executes actions using the real user’s authenticated session.
Java Options for Security Java and Java EE out-of-the-box security solutions are very comprehensive. They cover areas ranging from a low-level permission system, through cryptography APIs, to an authentication and authorization scheme. The list of security APIs offered in Java is very extensive, as the following list of the main ones shows:
12
•
Java Cryptography Architecture (JCA): This API offers support for cryptographic algorithms, including hash-digest and digital- signature support.
•
Java Cryptographic Extensions (JCE): This API mainly provides facilities for the encryption and decryption of strings and also secret key generation for symmetric algorithms.
•
Java Certification Path API (CertPath): This API provides comprehensive functionality for integrating the validation and verification of digital certificates into an application.
Chapter 1
The Scope of Security
•
Java Secure Socket Extension (JSSE): This API provides a standardized set of features to offer support for SSL and TLS protocols, both client and server, in Java.
•
Java Authentication and Authorization Service (JAAS): This API provides service for authentication and authorization in Java applications. It provides a pluggable system where authentication mechanisms can be plugged in independently to applications.
Please refer to this link for the entire list of Java 11 Security APIs: https://docs. oracle.com/en/java/javase/11/security/java-security-overview1.html#GUID- 2EF0B3B8-9F3A-41CF-A7DA-63DB52180084. Figure 1-7 shows the Java platform security architecture and elements.
Figure 1-7. Java platform security architecture and elements Spring Security’s main concerns are in the authentication/authorization realm. So it overlaps mainly with the JAAS Java API, although they can be used together, as you will see later in the book. Most of the other APIs are leveraged in Spring Security. For example, CertPath is used in X509AuthenticationFilter and JCE is used in the spring- security-crypto module.
13
Chapter 1
The Scope of Security
Summary In this chapter, we introduced security from a general point of view down to defense in depth (DiD) and its mechanisms. We explained in a very abstract way the main concerns in IT security and especially from an application point of view. We also described, very briefly, the main Java APIs that support security at different levels. You can see that this chapter was a very quick overview of security concerns. It is beyond the scope of this book to go any further than this on general topics, although we will study some of them in more depth when they apply to Spring Security. Obviously, this is nothing like a comprehensive software security guide, and if you are interested in learning more about software security in general, you should consult the specialized literature. The next chapter will introduce Spring Security as such.
14
CHAPTER 2
Introducing Spring Security In this chapter, you will learn what Spring Security is and how you can use it to address security concerns about your application. We will also describe what’s new in Spring Framework and Security version 5. How to use Spring Security v5 with authentication and authorization will be introduced a bit more in detail since we will be using them a lot in this book. Finally, we’ll take a look at the framework’s source code, how to build it, and the different modules that together form the powerful Spring Security project.
What Is Spring Security? Spring Security is a framework dedicated to providing a full array of security services to Java applications in a developer-friendly and flexible way. It adheres to the well- established practices introduced by the Spring Framework. Spring Security tries to address all the layers of security inside your application. In addition, it comes packed with an extensive array of configuration options that make it very flexible and powerful. Recall from Chapter 1 that it can be said that Spring Security is simply a comprehensive authentication/authorization framework built on top of the Spring Framework. Although the majority of applications that use the framework are web- based, Spring Security’s core can also be used in standalone applications. Many things make Spring Security immediately attractive to Java developers. To name just a few, consider the following list: •
It’s built on top of the successful Spring Framework. This is an important strength of Spring Security. The Spring Framework has become “the way” to build enterprise Java applications, and with
good reason. It is built around good practices and two simple yet powerful concepts: dependency injection (DI) and aspect-oriented programming (AOP). Also important is that many developers have experience with Spring, so they can leverage that experience when introducing Spring Security in their projects. •
It provides out-of-the-box support for many authentication models. Even more important than the previous point, Spring Security supports out-of-the-box integration with Lightweight Directory Access Protocol (LDAP), OpenID, form authentication, Certificate X.509 authentication, database authentication, Jasypt cryptography, and lots more. All this support means that Spring Security adapts to your security needs—and not only that, it can change if your needs change, without much effort involved for the developer. More info about Jasypt cryptography can be found at www.jasypt.org/.
This is important from a business point of view as well because the application can either adapt to the corporate authentication services or implement its own, thus requiring only straightforward configuration changes. This also means that there is a lot less software for you to write, because you are making use of a great amount of ready-to-use code that has been written and tested by a large and active user community. You can, to a certain point, trust that this code works and use it with confidence. And if it does not work, you can always fix it and send a patch to those in charge of maintaining the project. •
It offers layered security services. Spring Security allows you to secure your application at different levels, and to secure your web URLs, views, service methods, and domain model. You can pick and combine these features to achieve your security goals.
This is really flexible in practice. Imagine, for instance, that you offer services exposed through RMI, the Web, JMS, and others. You could secure all of these interfaces, but maybe it’s better to secure just the business layer so that all requests are secured when they reach this layer. Also, maybe you don’t care about securing individual business objects, so you can omit that module and use just the functionality you need.
16
Chapter 2
•
Introducing Spring Security
It is open source software. As part of the Pivotal portfolio, Spring Security is an open source software tool. It also has a large community and user base dedicated to testing and improving the framework. Having the opportunity to work with open source software is an attractive feature for most developers. The ability to look into the source code of the tools you like and work with is an exciting prospect. Whether our goal is to improve the tools or simply to understand how they work internally, we developers love to read code and learn from it.
Where Does Spring Security Fit In? Spring Security is without question a powerful and versatile tool. But like anything else, it is not a tool that adapts to everything you want to do. Its offerings have a defined scope. Where and why would you use Spring Security? Here is a list of reasons and scenarios: •
Your application is in Java, Groovy, or Kotlin. The first thing to take into account is that Spring Security can be written in languages like Java, Groovy, or Kotlin and generally in any language supported by the JVM. So if you plan to work in a non-JVM language, Spring Security won’t be of any use to you.
•
You need role-based authentication/authorization. This is the main use case of Spring Security. You have a list of users and a list of resources and operations on those resources. You group the users into roles and allow certain roles to access certain operations on certain resources. That’s the core functionality.
•
You want to secure a web application from malicious users. As mentioned, Spring Security is mostly used in web application environments. When this is the case, the first thing to do is allow only the users that you want to have access to your application, while forbidding all others from even reaching it.
17
Chapter 2
18
Introducing Spring Security
•
You need to integrate with OpenID, LDAP, Active Directory, and databases as security providers. If you need to integrate with a particular Users and Roles or Groups provider, you should take a look at the vast array of options Spring Security offers because integration might already be implemented for you, saving you from writing lots of unnecessary code. Sometimes you might not be exactly sure what provider your business will require to authenticate against. In this case, Spring Security makes your life easy by allowing you to switch between different providers in a painless way.
•
You need to secure your domain model and allow only certain users to access certain objects in your application. If you need finegrained security (that is, you need to secure on a per object, per user basis), Spring Security offers the Access Control List (ACL) module, which will help you to do just that in a straightforward way.
•
You want a nonintrusive, declarative way for adding security around your application. Security is a cross-cutting concern, not really a core business functionality of your application (unless you work in a security provider firm). As such, it is better if it can be treated as a separate and modular add-on that you can declare, configure, and manage independently of your main business concerns. Spring Security is built with this in mind. By using Servlet Filters, XML configuration, and AOP concepts, the framework tries not to pollute your application with security rules. Even when using annotations, they are still metadata on top of your code. They don’t mess with your code logic. You, as a Java developer, must try to isolate the Java Configuration into a configuration library and decouple it from the rest of the application in a similar way you do with XML.
•
You want to secure your service layer the same way you secure your URLs, and you need to add rules at the method level for allowing or disallowing user access. Spring Security allows you to use a consistent security model throughout the layers of your application because it internally enforces this consistent model itself. You configure users, roles, and providers in just one place, and both the service and web layers make use of this centralized security configuration in a transparent way.
Chapter 2
Introducing Spring Security
•
You need your application to remember its users on their next visit and allow them access. Sometimes you don’t want or need the users of your application to log in every time they visit your site. Spring supports out-of-the-box, remember-me functionality so that a user can be automatically logged in on subsequent visits to your site, allowing them full or partial access to their profile’s functionality.
•
You want to use public/private key certificates to authenticate against your application. Spring Security allows you to use X.509 certificates to verify the identity of the server. The server can also request a valid certificate from the client for establishing mutual authentication.
•
You need to hide elements in your web pages from certain users and show them to some others. View security is the first layer of security in a secured web application. It is normally not enough for guaranteeing security, but it is very important from a usability point of view because it allows the application to show or hide content depending on the user that is currently logged in to the system.
•
You need more flexibility than simple role-based authentication for your application. For example, suppose that you want to allow access only to users over 18 years of age using simple script expressions. Spring Security 3.1 uses the Spring Expression Language (SpEL) to allow you to customize access rules for your application.
•
You want your application to automatically handle HTTP status codes related to authorization errors (401, 403, and others). The built-in exception-handling mechanism of Spring Security for web applications automatically translates the more common exceptions to their corresponding HTTP status codes; for example, AccessDeniedException gets translated to the 403 status code.
•
You want to configure your application to be used from other applications (not browsers) and allow these other applications to authenticate themselves against yours. Another application accessing your application should be forced to use authentication mechanisms in order to gain access. For example, you can expose your application through REST endpoints that other applications can access with HTTP security. 19
Chapter 2
Introducing Spring Security
•
You are running an application outside a Java EE Server. If you are running your application in a simple web container like Apache Tomcat, you probably don’t have support for the full Java EE security stack. Spring Security can be easily leveraged in these environments.
•
You are running an application inside a Java EE Server. Even if you are running a full Java EE container, Spring Security is arguably more complete, flexible, and easy to use than the Java EE counterpart.
•
You are already using Spring in your application and want to leverage your knowledge of it. We explained before some of the great advantages of Spring. If you are currently using Spring, you probably like it a lot. So you will probably like Spring Security as well.
Spring Security Overview Spring Security v5 is no longer part of SpringSource but instead is now part of Pivotal. Spring by Pivotal includes many projects:
20
•
Spring Security
•
Spring Boot
•
Spring Framework
•
Spring Cloud Data Flow
•
Spring Cloud
•
Spring Data
•
Spring Integration
•
Spring Batch
•
Spring Hateoas
•
Spring Rest Docs
•
Spring Amqp
•
Spring Mobile
•
Spring For Android
Chapter 2
•
Spring Web Flow
•
Spring Web Services
•
Spring Ldap
•
Spring Session
•
Spring Shell
•
Spring Flo
•
Spring Kafka
•
Spring Statemachine
•
Spring Io Platform
•
Spring Roo
•
Spring Scala
•
Spring Blazeds Integration
•
Spring Loaded
•
Spring Xd
•
Spring Social
Introducing Spring Security
For more information, please refer to the Spring project web page at https:// spring.io/projects. All of these projects are built on top of the facilities provided by the Spring Framework itself, which is the original project that started it all. You can think of Spring as the hub of all these satellite projects, providing them with a consistent programming model and a set of established practices. The main points you will see throughout the different projects is the use of DI, XML namespace-based configuration, and AOP, which, as you will see in the next section, are the pillars upon which Spring is built. In the later versions of Spring, annotations have become the most popular way to configure both DI and AOP concerns. In this book, we will mainly introduce Spring Boot, analyze Spring Framework, and develop Spring Security version 5. Let’s start with Spring Boot.
21
Chapter 2
Introducing Spring Security
What is Spring Boot? Spring Boot is an open source Java-based framework which is generally used for developing microservice, enterprise-ready applications. It was developed by the Pivotal Team and helps developers to create stand-alone and production-ready Spring applications. Spring Boot is considered to be an easy starting point for building all Spring- based applications and running them as quickly as possible, with minimal upfront configuration of Spring.
Note Remember that a Spring Security application can be developed with Maven or Gradle. As we said, Spring Security is just one more of the Spring projects, and it is dedicated exclusively to addressing security concerns in your application. For more information, please refer to the documentation at https://spring.io/ projects/spring-security. Spring Security started originally as a non-Spring project. It was originally known as The Acegi Security System for Spring, and it was not the big and powerful framework it is today. Originally, it dealt only with authorization and leveraged container-provided authentication. Because of public demand, the project started to get traction, as more people started using it and contributing to its continuously growing code base. This eventually led to it becoming a Spring Framework portfolio project, and then later it was rebranded as “Spring Security.” Here are the Spring Security major releases dates: •
2.0.0 (April, 2008)
•
3.0.0 (December, 2009)
•
4.0.0 (March, 2015)
•
5.0.0 (November, 2017)
•
5.1.4 (February, 2019)
Notice that Java configuration for Spring Security was added to the Spring framework in Spring 3.1 and extended to Spring Security in Spring 3.2 and is defined in a class annotated @Configuration. 22
Chapter 2
Introducing Spring Security
So the project for many years now has been under the Pivotal umbrella of projects, powered by the Spring Framework itself. But what exactly is the Spring Framework?
Spring Framework 5: A Quick Overview We have mentioned the Spring Framework project quite a lot. It makes sense to give an overview of it at this point, because many of the Spring Security characteristics we will cover in the rest of the book rely on the building blocks of Spring. We admit we’re biased. We love Spring and have loved it for many years now. We think Spring has so many advantages and so many great things that we can’t start a new Java project without using it. Additionally, we tend to carry its concepts around when working with other languages and look for a way to apply them because they now feel so natural. The Spring Framework 5 overview is shown in Figure 2-1.
Figure 2-1. Spring Framework 5 Spring Framework 5 was published in September of 2017 and it can be considered the first major Spring Framework release since version 4 was released in December of 2013. 23
Chapter 2
Introducing Spring Security
Here is listed of the most important new things in Spring Framework 5: •
JDK baseline update, an upgrade to Java JDK 11
•
A reactive programming model. The Spring 5 framework is built on a reactive foundation and is fully asynchronous and nonblocking.
•
Programming with annotations. Spring 5 is now an annotation-based programming model.
•
New functional programming approach (including Kotlin)
•
Reactive-style programming with REST endpoints
•
HTTP/2 support
•
Kotlin and Spring WebFlux support
•
Lambdas support for bean registration
•
Spring WebMVC support for latest APIs
•
Testing improvements, such as conditional and concurrent testing with JUnit 5
•
Integration testing with Spring WebFlux
•
Core framework revision
•
General updates to the Spring core and container
•
Package cleansing and deprecation support
There are many things that attract us to Spring, but the main ones are the two major building blocks of the framework: dependency injection and aspect-oriented programming. Why are these two concepts so important? They are important because they allow you to develop loosely coupled, single-responsibility, DRY (Don’t Repeat Yourself ) code practically by default. These two concepts, and Spring itself, are covered extensively in other books and online tutorials; however, we’ll give you a brief overview here.
24
Chapter 2
Introducing Spring Security
D ependency Injection The basic idea of DI, a type of Inversion of Control (IoC), is simply that instead of having an object instantiate its needed dependencies, the dependencies are somehow given to the object. In a polymorphic way, the objects that are given as dependencies to the target object that depends on them are known to this target object just by an abstraction (like an interface in Java) and not by the exact implementation of the dependency. The major advantages of the IoC architecture are •
Easier switching between different implementations
•
Offering a good modularity of a program
•
A great feature for testing programs by isolating components dependencies and allowing them to communicate through contracts
•
Dividing the execution of a certain task from its implementation
It’s easier to look at this in code than explain it. See Listing 2-1.
Listing 2-1. The Object Itself Instantiates Its Dependencies (No Dependency Injection) public class NonDiObject { private Helper helper ; public NonDiObject ( ) { helper = new HelperImpl ( ) ; } public void doStuffWithHelp( ) { helper.help( ) ; } } In this example, every instance of NonDiObject is responsible for instantiating its own Helper in the constructor. You can see that it instantiates a HelperImpl, creating a tight, unnecessary coupling to this particular Helper implementation. Now look at Listing 2-2.
25
Chapter 2
Introducing Spring Security
Listing 2-2. The Object Receives Its Dependencies from Some External Source (with Dependency Injection) public class DiObject { private Helper helper ; public DiObject(Helper helper) { this.helper = helper; } public void doStuffWithHelp( ) { helper.help( ) ; } } In this version, the Helper is passed to the DiObject at construction time. DiObject is not required to instantiate any dependency. It doesn’t even need to know how to do that or what particular implementation type the Helper is, or where it comes from. It just needs a helper and uses it for whatever requirement it has. The advantage of this approach should be clear. The second version is loosely coupled to the Helper, depending only on the Helper interface, allowing the concrete implementation to be decided at runtime and thus giving lots of flexibility to the design. Spring dependency injection configuration is normally defined in XML files, although later versions have turned more to annotation-based configuration and Java- based configuration.
Aspect-Oriented Programming AOP is a technique for extracting cross-cutting concerns from the main application code and applying them in a transverse way across the points where they are needed. Typical examples of AOP concerns are transactions, logging, and security. The main idea is that you decouple the main business logic of your application from special-purpose concerns that are peripheral to this core logic, and then apply this functionality in a transparent, unobtrusive way through your application. By encapsulating this functionality (which is simply general application logic and not core business logic) in its own modules, they can be used by many parts of the application that need them, avoiding the need to duplicate this code all over the place. The entities that encapsulate this cross-cutting logic are referred to as Aspects in AOP terms. 26
Chapter 2
Introducing Spring Security
There are many implementations of AOP in Java. The most popular, perhaps, is AspectJ, which requires a special compilation process. Spring supports AspectJ, but it also includes its own AOP implementation, known simply as Spring AOP, which is a pure Java implementation that requires no special compilation process. Spring AOP using proxies is available only at the public-method level and just when it is called from outside the proxied object. This makes sense because calling a method from inside the object won’t call the proxy; instead, it calls the real self object directly (basically a call on the this object). This is very important to be aware of when working with Spring, and sometimes it is overlooked by novice Spring developers. Even when using its own AOP implementation, Spring leverages the AspectJ syntax and concepts for defining Aspects. Spring AOP is a fairly big subject, but the principle behind the way it works is not difficult to understand. Spring AOP works with the use of dynamically created proxy objects that take care of the AOP concerns around the invocation of your main business objects. You can think of the proxy and Spring AOP in general simply as a Decorator Pattern implementation, where your business object is the component and the AOP proxy is the decorator. Figure 2-2 shows a simple graphical representation of the concept. Thinking about it this way, you should be able to understand Spring AOP easily. Listing 2-3 shows how the magic happens conceptually.
Figure 2-2. Spring AOP in action
27
Chapter 2
Introducing Spring Security
Listing 2-3. The Business Object, not Transactional public class Business Object implements BusinessThing { public void doBusinessThing( ) { / / Some business stuff } } Suppose you have an aspect for transactions. Spring creates dynamically at runtime an object that conceptually looks like Listing 2-4.
Listing 2-4. Spring AOP Magic public class BusinessObjectTransactionalDecorator implements BusinessThing { private BusinessThing componen t ; public BusinessObjectTransactionalDecorator(BusinessThing component ) { t h i s . co mponent = component ; } public void doBusinessThing( ) { / / some start transaction code component.doBusinessThing( ) ; / / some commit transaction code } } Again, remember this simple idea and Spring AOP should be easier to understand.
What’s New in Spring Security 5? The previous version of this book utilizes Spring Security 3. Therefore, it is very important see the most important changes from version 3 to 5. They are the following:
28
•
Pivotal supports Spring Security since v5 because SpringSource does not exist anymore.
•
javax.servlet-api is used with v5 is 4.0.1.
Chapter 2
Introducing Spring Security
•
By default now, the ContextPath is /. Use /app_name if you need to define a specific contextPath or use the via properties; for instance, server.servlet.contextPath=/springbootapp.
•
Spring Security Filter Chain (CSRF): Since after v3 the CSRF token filter is added to the filter chain and turned on by default.
•
j_username/j_password parameters: Starting with v4, we no longer receive the username value in the authentication request. Plus, they were updated to username and password, removing the j_ prefix.
•
CSRF protection was added in v5.
•
Password encoding is mandatory in v5.
•
web.xml files are no longer needed starting with Servlet 3.0.
•
Easier Spring Security configurations using Java Configuration.
•
Possibility to use a combination with setting the debug level to DEBUG in the Log4J2 configuration file.
If you need to migrate from v3 to v5, we recommend to the following links showing how to migrate: •
Here are some of the most important new functionalities included in the Spring Security 5.1.4.RELEASE: •
Support for JDK 11
•
Automatic password storage upgrades through UserDetailsPasswordService
•
Support for OAuth 2.0 Client (for Servlet and WebFlux)
•
HTTP Firewall protection introduced
•
Support for LDAP Authentication which can now be configured with custom environment variables 29
Chapter 2
30
Introducing Spring Security
•
Support for X.509 Authentication for deriving the principal as a strategy
•
Support for Graal native image constraints
•
Upgraded to Reactor Core 3.2, Reactor Netty 0.8, ASM 7.0, and CGLIB 3.2.8
•
Support for Spring’s JCL bridge logging
•
Support for NIO.2 Path in FileSystemResource
•
Improved Core type and annotation resolution performance
•
Controller parameter annotations get detected on interfaces as well
•
Added Servlet requests parameters with HTTP PUT, PATCH, and DELETE
•
Added correlated log messages for HTTP requests and WebSocket sessions and control over DEBUG logging
•
More consistently detected method annotations
•
Support for HTTP/2 server when running with Reactor Netty 0.8
•
Improved DEBUG and TRACE logging
•
Support of Hamcrest and XML assertions in WebTestClient
•
Support for MockServerWebExchange to be configured with fixed WebSession
•
Improved, human-friendly, compact DEBUG and TRACE logging
•
Added control over DEBUG logging of potentially sensitive data
•
Updated web locale representation (e.g. Language tag, timezone cookies, etc.)
•
Added specific MVC exceptions for missing header, cookie, and path variables
•
Added base path for sets of annotated controllers (externally configured)
Chapter 2
Introducing Spring Security
•
Centralized handling of “forwarded” type headers
•
Support for logical and/or expressions in @Profile conditions
•
Support for serving Brotli
•
Empty collection/map/array injection in single constructor scenarios
•
Support for Hibernate ORM 5.3
•
Consistent non-exposure of null beans in the BeanFactory API
•
Refined Kotlin beans DSL
•
Programmatic ObjectProvider retrieval through the BeanFactory API
•
ObjectProvider iterable/stream access for beans-of-type resolution
•
Support for reactive clients in @MessageMapping methods
•
Support for preserve publication order of messages by STOMP broker
•
Supported @SendTo and @SendToUser to be used on the controller method
Spring Security 5 fundamentals include •
Authentication: Confirms truth of credentials
•
Authorization: Defines access policy for principal
•
AuthenticationManager: Controller in the authentication process
•
AuthenticationProvider: Interface that maps to a data store which stores your user data
•
Authentication Object: Object that is created upon authentication to hold the login credentials
•
GrantedAuthority: Application permission granted to a principal
•
Principal: User that performs the action
•
SecurityContext: Holds the authentication and other security information
•
SecurityContextHolder: Provides access to SecurityContext
31
Chapter 2
Introducing Spring Security
•
UserDetails: Data object that contains the user credentials, but also the roles of the user
•
UserDetailsService: Collects the user credentials and authorities (roles), and builds an UserDetails object
Here are some of the most important technologies Spring Security v5 supports integration with: •
HTTP
•
LDAP
•
OpenID
•
JAAS API
•
CAS
Note The current Spring Security 5.1.4.RELEASE can be downloaded at https://github.com/spring-projects/spring-security/releases/ tag/5.1.4.RELEASE. As previously seen, authentication and authorization are some of the fundamental functionalities in Spring Security 5.1. They are very important functionalities because they allow the Spring Security application to identify and authorize user as well as prevent unauthorized access and control the user authorization to access application resources. In this book, we will present some examples about how to develop application to authorize and authenticate users. The Spring Security authentication/authorization flow is shown in Figure 2-3.
32
Chapter 2
Introducing Spring Security
Figure 2-3. Spring Security authentication/authorization functionalities flow Spring Security is utilized via some specific modules as JAR files. The spring- security-core.jar file contains the core: •
Authentication and access-control classes and interfaces
•
Remoting support and basic provisioning APIs
Here are listed the JAR files required by any application where you will use Spring Security and supports: •
Standalone applications
•
Remote clients
•
Method (service layer) security
•
JDBC user provisioning
33
Chapter 2
Introducing Spring Security
The Spring Security v5 project’s most important modules (JAR files) include •
Core - spring-security-core.jar: •
org.springframework.security.core
•
org.springframework.security.access
•
org.springframework.security.authentication
•
org.springframework.security.provisioning
•
Remoting - spring-security-remoting.jar
•
Web - spring-security-web.jar
•
Config - spring-security-config.jar
•
LDAP - spring-security-ldap.jar
•
OAuth 2.0 Core - spring-security-oauth2-core.jar
•
ACL - spring-security-acl.jar
•
CAS - spring-security-cas.jar
•
OpenID - spring-security-openid.jar
•
Test - spring-security-test.jar
Please notice that both Spring Security XML and Java annotations can be used when developing Spring Security applications. In this book, both XML and Java annotations will be utilized for our examples.
Summary Right now, you should have a good idea of what Spring Security is and what it is useful for. You also learned what’s new in the Spring Security version 5. Along the way, we introduced some of the major architectural and design principles behind it and how they are layered on top of the great Spring Framework v5. We introduced dependency injection and AOP. In the next chapter, we will set up the development scene and you will build your first Spring Security–powered web application.
34
CHAPTER 3
Setting Up the Scene This chapter will guide you through the process of building your first simple Spring Security v5 project using the IntelliJ IDEA Ultimate Edition 2019.2. This involves the following steps: 1. Setting up the development environment 2. Creating a new Java Web Application project without Spring Security 3. Updating the project with Spring Security 4. Running the example Let’s start with setting up the development environment.
Setting Up the Development Environment Here’s the list of software you’ll need to download and install in the given order: •
Java SE Development Kit (JDK) 11
•
Maven 3.6.1
•
IntelliJ IDEA Ultimate Edition 2019.2
•
Apache Tomcat Server v9 (External)
Let’s go through the steps required to set up everything properly. Your first step is to set up the Java SE Development Kit. On most operating systems, the JDK comes in an installer or package, so there shouldn’t be any problems.
Note Remember that the Java SE Development Kit and Java SE Runtime Environment (JRE) require at minimum a Pentium 2 266 MHz processor, 128MB of memory, and 181MB disk for development tools for 64-bit platforms. Download the JDK version specific to your Windows operating system from the following link: www.oracle.com/technetwork/java/javase/downloads/jdk11-downloads-5066655.html. We will use the JDK version 11 in this book. Once you have installed the JDK on your Windows 10 machine, remember to set a JAVA_HOME system variable by following these steps: 1. Open the Windows Environment Variables. 2. Add the JAVA_HOME variable and point it to the JDK installed folder (in my case, C:\Program Files\Java\jdk-11.0.2). 3. Append %JAVA_HOME%\bin to the system PATH variable so that all of the Java commands will be accessible from everywhere. The result is shown in Figure 3-1.
36
Chapter 3
Setting Up the Scene
Figure 3-1. Setting up the JAVA_HOME system variable Let’s test if the JDK installation was successful. Open a command prompt and type the code shown in Figure 3-2.
Figure 3-2. Testing the JAVA installation
37
Chapter 3
Setting Up the Scene
Great! Java is now installed and ready to be used for the examples in the book. Let’s now install the IntelliJ IDEA Ultimate Edition 2019.2 for web and enterprise development by following these steps: 1. Download the .exe file from www.jetbrains.com/idea/download /#section=windows. 2. Install the .exe file, which in our case is named ideaIU2019.2.exe. Once installed, the directory should look like Figure 3-3.
Figure 3-3. The IntelliJ IDEA 2019.2 directory Now IntelliJ IDEA Ultimate Edition 2019.2 for web and enterprise development tool is ready to be used. Figure 3-4 shows how the dashboard looks when executing it.
38
Chapter 3
Setting Up the Scene
Figure 3-4. The IntelliJ IDEA Ultimate Edition 2019.2 for web and enterprise development dashboard The next step is to install Maven 3.6.1 by downloading the .zip file named apache- maven-3.6.1-bin.zip at this web page: https://maven.apache.org/download.cgi. Now run the IntelliJ IDEA 2019.2 tool and configure Maven 3.6.1 as shown in Figure 3-5.
39
Chapter 3
Setting Up the Scene
Figure 3-5. Local Maven 3.6.1 is configured into IntelliJ IDEA 2019.2 Now Maven 3.6.1 is ready to be used. The last tool used in this book is the Apache Tomcat Server and plugin v9. The first step is to download and install the Apache Tomcat Server v9 .zip file named apache- tomcat-9.0.16.zip at https://tomcat.apache.org/download-90.cgi. Unzip the file in a certain directory onto your Windows machine, like C:\Program Files\Apache Software Foundation\apache-tomcat-9.0.22. Since you need to allow Spring projects to deploy to Tomcat Servers, you need to define Tomcat users to access to Tomcat Manager. Let’s open and update the file tomcat-users.xml in the conf directory and add the following XML fragment inside the element: 40
Chapter 3
Setting Up the Scene
Make sure you add the Tomcat Server to IntelliJ. Now Apache Tomcat Server and plugin v9 are ready to be used. Before starting a new spring project, you want to make sure the right JDK package is installed into IntelliJ IDEA 2019.2 IDE tool to compile your examples and avoid the typical compiling issue where the JRE is found instead of JDK. The configuration is shown in Figure 3-6.
Figure 3-6. Configuring the JDK to compile your examples So now the JDK compiler is set and you are ready to start writing and running your first Spring Web application example.
41
Chapter 3
Setting Up the Scene
Creating a New Java Web Application Project With your development tools set up, you can now create your first Java Web Application project using IntelliJ IDEA 2019.2. The built-in wizard makes creating a new Maven project very easy. So, let’s create your first Java Web application named Pss01, without security, which will just produce the following text: Hello Spring Security! Here are the steps you will follow to build the simple Maven Web Application project: 1. Create a Java Web Application v4. 2. Create and update the needed .jsp file. 3. Run the Java Web Application using the external Tomcat Server v9. As first step, launch the IntelliJ IDEA tool and select File ➤ New ➤ Project ➤ Java ➤ Web Application and fill all information about the project, as shown in Figure 3-7.
Figure 3-7. Your first Java Web Application project In the Package Explorer, you should now see your Pss01 project. If you expand it and all its children, you’ll see something like Figure 3-8.
42
Chapter 3
Setting Up the Scene
Figure 3-8. Your first Java Web Application project structure In general, the structure of most Java Web Application projects will contain •
The target directory: Used to house all output of the build
•
The src directory: Contains all of the source material for building the project, its site, etc.
•
src/main/java: Application/library sources
•
src/main/resources: Application/library resources
•
web: Web application sources
•
Pom.xml: File description of the project
Your next step is to update the Java Web Application project’s files needed for your first simple application. Please note that for this simple Java Web Application example, you will not need to add any specific dependency to the project file pom.xml, which looks initially like Listing 3-1.
43
Chapter 3
Setting Up the Scene
Listing 3-1. The pom.xml File with Servlet Dependencies 4.0.0com.apress.pssPss011.0-SNAPSHOT The project right now only contains one simple .jsp file named index.jsp, which you will update to show the text you wish, as shown in Listing 3-2.
Next, click the Add Configuration button, located at the top-right of the IntelliJ tool, to configure how to run your first example. You can run your project using the external Tomcat Server v9, as shown in Figure 3-9.
44
Chapter 3
Setting Up the Scene
Figure 3-9. Configure the running steps of your first Maven project Now you can open you web browser and type the web address http:// localhost:8080, as shown in Figure 3-10.
Figure 3-10. The Java Web Application project running in a web browser Your first Java Web Application project is now ready to be transformed into a Spring Security project.
45
Chapter 3
Setting Up the Scene
Creating a New Spring Security v5 Project Spring Security builds upon the concepts defined in the previous section and integrates nicely into the general Spring ecosystem. You need to understand those concepts well to take maximum advantage of Spring Security v5. However, you can start using Spring Security without really knowing all these details, and then learn them as you progress and look to do more advanced things. There are two ways to create a new Spring project. You can either create a Spring project via Spring Boot or via any IDE tool, which in this case is IntelliJ IDEA 2019.2. So what is Spring Boot? Spring Boot can be used to easily generate standalone, production-grade, Spring-based applications. To bootstrap your Spring application, you can utilize the Spring Initializr. The Spring Initializr is a browser-based web application and a REST API that will generate a ready skeleton Spring project structure for your Spring application. To generate a Spring application via Spring Boot, go to the following web page and fill in all the required info: https://spring.io/projects/spring-boot. Please notice that in this book all the Spring code is developed using the IntelliJ IDEA Ultimate Edition 2019.2 tool. What kind of Spring Security Maven Web Application do you want to create? Let’s create a simple Spring Security example where if the user requests a welcome page, they will not need to authenticate when accessing the Admin page, which would require the admin security credentials via username and password. If you are using the standalone installation of Spring Security reference release and you decide not to use any IDE tool to build your Maven project, you will find many folders inside the installation directory. Most of the folders in the directory correspond to individual subprojects or modules that split the functionality of Spring Security into more discrete and specialized units.
Spring Security v5 Source Open source software has an invaluable characteristic for software developers: free access to all source code. With this, we can understand how our favorite tools and frameworks work internally, and we also can learn a lot about the way other (perhaps very good) developers work, including what practices, techniques, and patterns they use. Free access to source code also enables us, in general, to gather ideas and experience 46
Chapter 3
Setting Up the Scene
for our own development. As a more practical matter, having access to the source code allows us to debug these applications in the context of our application; we can find bugs or simply follow our application’s execution through them. Currently, Spring Security and most Spring projects live in GitHub. You probably know about GitHub (https://github.com/). If you don’t, you should definitely take a look at it because it has become a standard public source-code repository for many open source projects in a multitude of programming languages. GitHub is a repository and a hosting service for Git repositories, with a very friendly management interface. The Spring Security project can be found inside the SpringSource general GitHub section at https://github.com/SpringSource/spring- security. To get the code, just download and install it as shown previously in this chapter. If you need to install the Spring Security 5.1.5.RELEASE locally, once unzipped it will include several modules and folders, as shown in Figure 3-11.
Figure 3-11. The Spring Security v5.1.5.RELEASE folder structure 47
Chapter 3
Setting Up the Scene
Here is a short description of some of the most important modules included in Spring Security v5.1.5.RELEASE:
48
•
Core (spring-security-core): Spring Security’s core classes and interfaces on authentication and access control reside here.
•
Remoting (spring-security-remoting): In case you need Spring Remoting, this is the module with the necessary classes.
•
Aspect (spring-security-aspects): Aspect-oriented programming support within Spring Security.
•
Config (spring-security-config): Provides XML and Java configuration support.
CAS (spring-security-cas): CAS (Central Authentication Service) client integration.
•
TagLib (spring-security-taglibs): Various tag libraries regarding Spring Security.
•
Test (spring-security-test): Testing support.
•
Web (spring-security-web): Contains web security infrastructure code, such as various filters and other Servlet API dependencies.
Chapter 3
Setting Up the Scene
Note Remember that you are using the IntelliJ IDEA tool where the Spring Security 5.1.5.RELEASE is integrated and configured in it. The usage of Spring Security is done via an XML link at the beginning of the pom.xml file. Let’s build your Spring Security 5 Java Web Application. Here are the steps you will follow to build the simple Spring Security Maven Web Application project: •
Clone the previous simple Maven project named Pss01.
•
Import the required Spring Framework and Spring Security v5 libraries into the project (into the pom.xml file).
•
Configure the project to be aware of Spring Security.
•
Configure the users and roles that will be part of the system.
•
Configure the URLs that you want to secure.
•
Create all needed Java and web files.
•
Run the Spring Security v5 project using the external Tomcat Server v9.
As a first step, you will clone this project and call it pss01_Security, so you will find both samples when downloading the code. Once you clone the project from Pss01 to pss01_Security, you add the Maven framework support to the project. All the files and the pss01_Security project structure are shown in Figure 3-12.
49
Chapter 3
Setting Up the Scene
Figure 3-12. New Maven project to add Spring Security
Note It is possible to implement the Spring Security in a Spring application using XML- or Java-based configurations. In this chapter, you will use the Java configuration for your Spring Security Web Application since in general it is hardly suggested to use XML configuration as minimum as possible.
Spring Security Taglibs The Spring Security Taglibs provides the support for accessing security information and applying security constraints in JSPs pages. To secure the view layer of the application, you can use
50
•
Authorize Tag
•
Authentication Tag
•
Accesscontrollist Tag
•
Csrfinput Tag
•
CsrfMetaTags Tag
Chapter 3
Setting Up the Scene
You will need to add the spring-security-taglibs dependency to your pom.xml: org.springframework.securityspring-security-taglibs5.1.5.RELEASE
Introducing Cross-Site Request Forgery With a cross-site request forgery (CSRF) attack a hacker can modify the state of any HTTP method (GET or POST), redirecting the client, for instance, by clicking a modified link to a not-secure web page with the result of stealing a user’s sensitive info. Let’s have a look to what CSRF is and how to prevent CSRF attacks using Spring Security. Common CSRF attacks include •
HTTP GET Request: Convincing the victim to click a fake GET link to get sensitive information (e.g. username/password, etc.)
•
HTTP POST request: Same as GET but using the POST method
In general, to use the Spring Security CSRF protection, we must ensure the right HTTP methods like (PATCH, POST, PUT, DELETE, etc.) can modify state. In Spring Security v5, CSRF protection is enabled by default in the Java configuration. If you must disable it, you can add the following: .csrf().disable(). If you need to enable the CSRF protection on the server side, you need to include the CSRF token in your requests on the client side by adding this line: .
dding Spring Security v5 (and Spring Core Itself) A to the Project In this section, you’ll start your journey into the inner workings of the framework and see its main building blocks and how it works. The information will be mostly introductory. We’ll conduct a full, in-depth review of the framework in the next chapter. We could just 51
Chapter 3
Setting Up the Scene
tell you what to add to the project to make the application work, but we think it’s better to tell you first what the different components of the framework are so that you can start coding with a better knowledge of how the framework is built. This means that we will tell you how to grab the source code of the project and build it, and then explain in a general way the different modules that make up the framework. Your next step is to add the Spring Security 5.1.4.RELEASE version, shown in Listing 3-3, in your Maven Web Application by adding to the pom.xml file the following JAR dependencies configuration: •
spring-security-web
•
spring-security-config
•
spring-security-taglibs
Listing 3-3. Spring Security Maven Dependencies org.springframework.securityspring-security-web5.1.5.RELEASEorg.springframework.securityspring-security-config5.1.5.RELEASEorg.springframework.securityspring-security-taglibs5.1.5.RELEASE The entire updated pom.xml file is shown in Listing 3-4.
52
Chapter 3
Setting Up the Scene
Listing 3-4. Adding Spring Security Configuration to the pom.xml File 4.0.0groupIdpss01_Security1.0-SNAPSHOT5.1.5.RELEASE5.1.5.RELEASEorg.springframeworkspring-core${springframework.version}org.springframeworkspring-web${springframework.version}org.springframeworkspring-webmvc${springframework.version}
srcorg.apache.maven.pluginsmaven-compiler-plugin3.8.111pss01_Security Please notice that spring-webmvc is the only dependency needed since spring-core and spring-web are transitive dependencies. The pom.xml file contains the following: •
The dependencies needed for the project, such as Spring, Spring Security, Servlet API, and TagLibs.
•
The element containing the plugins used, such as •
The maven-compiler-plugin, to specify the Java version that will be used in the project
•
The Servlet information
•
The TagLibs
•
The maven-war-plugin needed to build the project into a WAR file, which will be deployed to Tomcat in this case
Note Remember that no Spring Boot dependencies were used in this example. 55
Chapter 3
Setting Up the Scene
onfiguring the Web Project to Be Aware of Spring C Security v5 To activate Spring Security in your Maven Web Application, you need to configure a particular Servlet filter that will take care of preprocessing and postprocessing the requests, as well as managing the required security constraints. Let’s start building your Spring Security Maven Web Application. As a first step, please make sure that all the tools and directories are created as described previously. Then update the pom.xml file as shown in Listing 3-4. Next, create the needed .jsp files under a new project directory called WebContent/ WEB-INF/pages/. Your project will utilize two .jsp pages: •
welcome.jsp, which is the starting welcome web page of the project
•
authenticated.jsp, which is the admin web page to access when the user successfully logs in
The welcome.jsp page will only display a welcoming message and provide the link to the authenticated page, /authenticated. Let’s now create the authenticated.jsp page; see Listing 3-6.
Welcome to Spring Security authentication example!
Your username is: ${user} You provided Admin authentication credentials! You provided User authentication credentials!
">Logout If the login is successful, the user will access the authenticated.jsp page, which we will explain later. Next, you need to define the Java classes needed for your example. Under controller: •
UserController
57
Chapter 3
Setting Up the Scene
Under package configuration: •
AppInitializer
•
SecurityConfiguration
•
SpringSecurityInitializer
•
UserConfiguration
Let’s create the two Java packages where your Java classes will be located: •
com.apress.pss.springsecurity.controller
•
com.apress.pss.springsecurity.configuration
Let’s create the UserController Java class under the package com.apress.pss. springsecurity.controller, as shown in Listing 3-7.
This Java class will basically be used to handle and control the requests of your web pages. So, using @GetMapping, this class will handle the web request such as welcome, authenticated, and logout. Additionally, this class via the function named getPrincipal will extract the name of the user logged in to be shown on the authenticated.jsp page. Notice that @GetMapping replaced @RequestMapping. The next Java class to create is AppInitializer, which initializes Spring classes, such as AnnotationConfigDispatcherServletInitializer to be used in your applications; see Listing 3-8.
Listing 3-8. appInitializer.java package com.apress.pss.springsecurity.configuration; import org.springframework.web.servlet.support. AbstractAnnotationConfigDispatcherServletInitializer; public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class>[] getRootConfigClasses() { return new Class[] { UserConfiguration.class }; } @Override protected Class>[] getServletConfigClasses() { return null; } @Override protected String[] getServletMappings() { return new String[] { "/" }; } } The next Java class to create is SpringSecurityInitializer, which is an empty Java class that initializes the Spring Security classes, such as the AbstractSecurityWebApplicationInitializer, to be used in your applications; see Listing 3-9. 60
Chapter 3
Setting Up the Scene
Listing 3-9. SpringSecurityInitializer.java package com.apress.pss.springsecurity.configuration; import org.springframework.security.web.context. AbstractSecurityWebApplicationInitializer; public class SpringSecurityInitializer extends AbstractSecurityWebApplicationInitializer { //code is not needed } The next Java class to create is UserConfiguration, which defines where the Java classes and .jps pages needed for your application are located. In your case, the web resources to handle are located in /WEB-INF/pages/ as .jsp pages. The UserConfiguration Java class is shown in Listing 3-10.
@Configuration @EnableWebMvc @ComponentScan(basePackages = "com.apress.pss.springsecurity") public class UserConfiguration { @Bean(name="SpringSecurity") public ViewResolver viewResolver() { InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setViewClass(JstlView.class); viewResolver.setPrefix("/WEB-INF/views/"); viewResolver.setSuffix(".jsp"); 61
Chapter 3
Setting Up the Scene
return viewResolver; } } The last Java class to create is SecurityConfiguration, which defines the security configuration for your Spring Security v5 application; see Listing 3-11.
.withUser("user").password(passwordEncoder.encode("user123")).roles("USER") .and() .withUser("admin").password(passwordEncoder.encode("admin123")).roles("USER", "ADMIN"); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/", "/welcome", "/login").permitAll() .antMatchers("/authenticated/**").hasAnyRole("ADMIN", "USER") .and().formLogin() .and().logout().logoutSuccessUrl("/welcome").permitAll() .and().csrf().disable(); } } Let’s analyses this Java class in detail. You define all security configuration needed in SecurityConfiguration and then you define the PasswordEncoder, which returns the BCryptPassword. You then define the page seen by, like welcome.jsp, and then the security role needed to access the authenticated.jsp page, such as admin and user. The logout will redirect to the welcome.jsp page and finally the csrf will be disabled. The structure of your new Spring Security v5 project should look like Figure 3-13.
63
Chapter 3
Setting Up the Scene
Figure 3-13. New Spring Security v5 project structure Next, build and run the Spring Security v5 project as shown in Figure 3-14.
64
Chapter 3
Setting Up the Scene
Figure 3-14. Project running configuration You can now build the project, deploy the WAR file, start the application running on the standalone Tomcat Server v9, and deploy the WAR file automatically. Your application is deployed successfully. Open the web browser and type the following link: http://localhost:8080/pss01_Security/. The outcome is shown in Figure 3-15.
Figure 3-15. Browsing the new Spring Security project You can now access the security authenticated.jsp by clicking the Login link. The outcome is shown in Figure 3-16. 65
Chapter 3
Setting Up the Scene
Figure 3-16. Accessing the Spring Security login web page Now, if you access with the wrong credentials, you will receive an error message like the one in Figure 3-17.
Figure 3-17. Accessing with wrong login credentials As you can see, Spring Security will directly produce the login error and remind the user that the credentials provided are not correct. If you next provide the right user or admin credentials, you will receive the content defined in the authenticated.jsp page which, using the taglibs, identifies if an admin or user credential is provided, displays a welcome message, and provides the username using ${user}, who logged in. If you log in as the correct admin, you will see the result shown in Figure 3-18.
66
Chapter 3
Setting Up the Scene
Figure 3-18. Accessing with the right admin credentials If you log in as a correct user, you will receive the result shown in Figure 3-19.
Figure 3-19. Accessing with the right user credentials Notice that when you log in as a user, the text saying that “You provided User authentication credentials!” is shown, while when you log in as an admin, the text saying, “You provided Admin authentication credentials!” is shown. This was controlled via the Spring Security Taglibs. The admin login iteration flow is shown in Figure 3-20.
67
Chapter 3
Setting Up the Scene
Figure 3-20. Spring Security user with Admin role authentication request flow Great! You have built your first Spring Security web application. We will dive deeply into how all this works internally when we look at the architecture of Spring Security.
Summary We introduced all the tools needed to create the environment to develop Spring Security Java Web Applications. You learned how to install and configure all the tools needed for these examples and you should have a good idea of what is needed to build a Spring Security v5 project. You learned how to build your first Java Web Application project without Spring Security and then you added the security dependencies to update it as a Spring Security v5 application. In the next chapter, we’ll go deep into the architecture and design of the Spring Framework.
68
CHAPTER 4
Spring Security Architecture and Design In the previous chapter, you developed an initial application secured with Spring Security. You got an overview of the way this application worked and looked in detail at some of the Spring Security components that are put into action in common Spring Security–secured applications. In this chapter, we are going to extend those explanations and delve deeply into the framework. We’ll look at the main components of the framework, explain the work of the servlet filters for securing web applications, look at how Spring aspect-oriented programming helps you add security in an unobtrusive way, and in general, show how the framework is designed internally.
What Components Make Up Spring Security? In this section, we’ll take a look at the major components that make Spring Security work. We’ll offer a big-picture overview of the framework and then delve deeper into each major component.
The 10,000-Foot View Spring Security is a relatively flexible framework that aims to make it easy for the developer to implement security in an application. At the most general level, it’s a framework composed of intercepting rules for granting, or not granting, access to resources. Figure 4-1 illustrates this.
Figure 4-1. Spring Security 10,000-foot overview From this view, you can think of Spring Security simply as an extra layer built on top of your application, wrapping specific entry points into your logic with determined security rules.
The 1,000-Foot View Going into a little more detail, we arrive at AOP and servlet filters. Spring Security’s interception model of security applies to two main areas of your application: URLs and method invocations. Spring Security wraps around these two entry points of your application and allows access only when the security constraints are satisfied. Both the method call and the filter-based security depend on a central Security Interceptor, where the main logic resides to make the decision whether or not access should be granted. In Figure 4-2, you can see this more detailed overview of the framework.
70
Chapter 4
Spring Security Architecture and Design
Figure 4-2. In this view, both method calls and HTTP requests try to access a resource, but first they must go through the Security Interceptor
The 100-Foot View Spring Security might seem simple conceptually, but internally there is a lot going on in a very well-built software tool. This next overview will show you the main collaborating parts that participate in the general process of ensuring that your security constraints are enforced. This is particularly achievable with an open source project like Spring Security which allows you to get into the framework itself and appreciate its design and architecture by accessing the source code directly. After that, we’ll delve deeper into the implementation details. For us, what follows is the best way to understand Spring Security from the inside. The enumeration of what we consider to be the main components of the framework will help you know where everything belongs and how your application is enforcing the security rules that you specify for it. The most important Spring Security internal architecture core modules are •
Authentication
•
Authorization
The process of the Authentication and Authorization modules were introduced in Chapter 1.
71
Chapter 4
Spring Security Architecture and Design
Figure 4-3 provides an illustration of all the concept/components.
Figure 4-3. The key components of Spring Security
The Security Interceptor One of the most important components of the framework is the Security Interceptor. With the main logic implemented in AbstractSecurityInterceptor and with two concrete implementations in the form of FilterSecurityInterceptor and 72
Chapter 4
Spring Security Architecture and Design
MethodSecurityInterceptor (as shown in Figure 4-4), the Security Interceptor is in charge of deciding whether a particular petition should be allowed to go through to a secured resource. MethodSecurityInterceptor, as its name should tell you, deals with petitions directed as method calls, while FilterSecurityInterceptor deals with petitions directed to web URLs. The Security Interceptor works with a preprocessing step and a postprocessing step. In the preprocessing step, it looks to see whether the requested resource is secured with some metadata information (or ConfigAttribute). If it is not, the request is allowed to continue its way either to the requested URL or method. If the requested resource is secured, the Security Interceptor retrieves the Authentication object from the current SecurityContext. If necessary, the Authentication object will be authenticated against the configured AuthenticationManager with the following method: public interface AuthenticationManager { Authentication authenticate(Authentication authentication) throws AuthenticationException; } An AuthenticationManager can do mainly three things with its method: •
Return an Authentication with value authenticated=true if the input represents a valid principal and can be verified
•
Throw an AuthenticationException if the input represents an invalid principal
•
Return null if it can’t decide
Notice that ProviderManager (which delegates to a chain of AuthenticationProvider instances) is the most commonly used implementation of AuthenticationManager. An example of the AuthenticationManager hierarchy using ProviderManager is shown in Figure 4-4.
73
Chapter 4
Spring Security Architecture and Design
Figure 4-4. AuthenticationManager hierarchy using ProviderManager After the object is authenticated, AccessDecisionManager is called to determine if the authenticated entity is able to finally access the resource. AccessDecisionManager throws an AccessDeniedException if the authenticated entity is not allowed to access the resource. If AccessDecisionManager decides that the Authentication entity is allowed to access the resource, the Authentication object is passed to RunAsManager if this is configured. If RunAsManager is not configured, a no-op implementation is called. RunAsManager returns either null (if it’s not configured to be used) or a new Authentication object containing the same principal, credentials, and granted authorities as the original Authentication object, plus a new set of authorities based on the RUN_AS that is being used. This new Authentication object is put into the current SecurityContext. After this processing, and independently of whether or not a RUN_AS Authentication object is used, the Security Interceptor creates a new InterceptorStatusToken with information about the SecurityContext and the ConfigAttributes. This token will be used later in the postprocessing step of the Security Interceptor. At this point, the Security Interceptor is ready to allow access to the secured resource, so it passes the invocation through and the particular secured entity (either a URL or a method) is invoked. After the invocation returns, the second phase of the Security Interceptor comes into play, and the postprocessing begins. The postprocessing step is considerably simpler, and it involves only 74
Chapter 4
Spring Security Architecture and Design
calling an AfterInvocationManager's decide method if there is one configured. In its current implementation, AfterInvocationManager delegates to instances of PostInvocationAuthorizationAdvice, which ultimately filters the returned objects or throws an AccessDeniedException if necessary. This is the case if you are using the postinvocation filters in method-level security, as you will see in the following chapter. In the case of web security, the AfterInvocationManager is null. That is a lot of work for the Security Interceptor. However, because the framework is nicely modular at the class level, you can see that the Security Interceptor simply delegates most of the task to a series of well-defined collaborators, which in a very SRP (Single Responsibility Principle) way focus on single, narrowly scoped responsibilities. This is good software design and an example you should emulate. As shown in Listing 4-1, you paste the main parts of the code from the AbstractSecurityInterceptor itself so that you can see the things we’ve been talking about. We include some comments in the code so that you can understand better what it does; they start with // ----. Please notice that the entire AbstractSecurityInterceptor course code can be found on the GitHub address at https://github.com/spring-projects/spring- security/blob/master/core/src/main/java/org/springframework/security/ access/intercept/AbstractSecurityInterceptor.java.
Listing 4-1. AbstractSecurityInterceptor protected InterceptorStatusToken beforeInvocation(Object object) { Assert.notNull(object, "Object was null"); final boolean debug = logger.isDebugEnabled(); // --- Here we are checking if this filter is able to process a particular type of object. For example FilterSecurityInterceptor is able to process FilterInvocation objects. MethodSecurityInterceptor is able to process MethodInvocation objects. if (!getSecureObjectClass().isAssignableFrom(object.getClass())) { throw new IllegalArgumentException("Security invocation attempted for object " + object.getClass().getName() + " but AbstractSecurityInterceptor only configured to support secure objects of type: " + getSecureObjectClass()); } 75
Chapter 4
Spring Security Architecture and Design
// ---- Here we are retrieving the security metadata that maps to the object we are receiving. So if we are receiving a FilterInvocation, the request is extracted from it and used to find the ConfigAttribute (s) that match the request path pattern Collection attributes = this. obtainSecurityMetadataSource().getAttributes(object); if (attributes == null || attributes.isEmpty()) { if (rejectPublicInvocations) { throw new IllegalArgumentException("Secure object invocation " + object + " was denied as public invocations are not allowed via this interceptor. " + "This indicates a configuration error because the " + "rejectPublicInvocations property is set to 'true'"); } if (debug) { logger.debug("Public object - authentication not attempted"); } publishEvent(new PublicInvocationEvent(object)); return null; // no further work post-invocation } if (debug) { logger.debug("Secure object: " + object + "; Attributes: " + attributes); } if (SecurityContextHolder.getContext().getAuthentication() == null) { credentialsNotFound(messages.getMessage("AbstractSecurityInterceptor. authenticationNotFound", "An Authentication object was not found in the SecurityContext"), object, attributes); }
76
Chapter 4
Spring Security Architecture and Design
Authentication authenticated = authenticateIfRequired(); // ---- Here we are calling the decision manager to decide if authorization is granted or not. This will trigger the voting mechanism, and in case that access is not granted an exception should be thrown. try { this.accessDecisionManager.decide(authenticated, object, attributes); } catch (AccessDeniedException accessDeniedException) { publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, accessDeniedException)); throw accessDeniedException; } if (debug) { logger.debug("Authorization successful"); } if (publishAuthorizationSuccess) { publishEvent(new AuthorizedEvent(object, attributes, authenticated)); } // ---- Here it will try to use the run-as functionality of Spring Security that allows a user // --to impersonate another one acquiring its security roles, or more precisely, its //--GrantedAuthority (s) Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes); if (runAs == null) { if (debug) { logger.debug("RunAsManager did not change Authentication object"); }
77
Chapter 4
Spring Security Architecture and Design
// no further work post-invocation return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object); } else { if (debug) { logger.debug("Switching to RunAs Authentication: " + runAs); } SecurityContext origCtx = SecurityContextHolder.getContext(); SecurityContextHolder.setContext(SecurityContextHolder. createEmptyContext()); SecurityContextHolder.getContext().setAuthentication(runAs); // need to revert to token.Authenticated post-invocation return new InterceptorStatusToken(origCtx, true, attributes, object); } // ---- If the method has not thrown an exception at this point, it is safe to continue // ---- the invocation through to the resource. Authorization has been granted. } protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) { if (token == null) { // public object return returnedObject; } if (token.isContextHolderRefreshRequired()) { if (logger.isDebugEnabled()) { logger.debug("Reverting to original Authentication: " + token. getSecurityContext().getAuthentication()); } SecurityContextHolder.setContext(token.getSecurityContext()); }
78
Chapter 4
Spring Security Architecture and Design
// ---- If there is an afterInvocationManager configured, it will be called. // ---- It will take care of filtering the return value or actually throwing an exception //----- if it is relevant to do so. if (afterInvocationManager != null) { // Attempt after invocation handling try { returnedObject = afterInvocationManager.decide(token.getSecurityContext(). getAuthentication(), token.getSecureObject(), token.getAttributes(), returnedObject); } catch (AccessDeniedException accessDeniedException) { AuthorizationFailureEvent event = new AuthorizationFailureEvent(token. getSecureObject(), token .getAttributes(), token.getSecurityContext(). getAuthentication(), accessDeniedException); publishEvent(event); throw accessDeniedException; } } // ---- Here is the full authorization cycled finished. The response is returned to the caller. return returnedObject; } The Security Interceptor lies at the core of the Spring Security framework. Every call to a secured resource in Spring Security passes through this interceptor. The AbstractSecurityInterceptor shows its versatility when you realize that two not- very-related kinds of resources (URL endpoints and methods) leverage most of the functionality of this abstract interceptor. This, once again, shows the effort put into the design and implementation of the framework. Figure 4-5 shows the interceptor in a UML (Unified Modeling Language) class diagram. And Figure 4-6 shows a simplified sequence diagram. 79
Chapter 4
Spring Security Architecture and Design
Figure 4-5. SecurityInterceptor UML class diagram, simplified
We know how the Security Interceptors work, but how do they come to be? How do they know what to intercept? The answer to that lies in the next few components, so keep reading.
The XML Namespace The XML namespace is of extreme importance to the general appeal and usability of the framework, yet it is, in theory, not strictly necessary. If you know how the Spring Framework’s namespaces work, you probably have a good idea of what is going on when you define your security-specific XML configuration in your application context definition files. If you don’t know how they work, maybe you think Spring is somehow made aware of how to treat these specific elements and how to load them in the general Spring application context. Either way, here we will explain in some detail the process behind the definition of a custom namespace in Spring, and particularly, the elements in the Spring Security namespace. Originally, Spring did not support custom XML. All that Spring understood was its own classes defined in the standard Spring Core namespace, where you can define s on a bean-to-bean basis and can’t really define anything conceptually more complex without adding that complexity yourself to the configuration. This -based configuration was, and still is, very good for configuring general- purpose bean instances, but it can get messy really fast for defining more domain- specific utilities. And beyond being messy, it is also very poor at expressing the business domain of the beans you are defining. We’ll explore this manual configuration later in the book, but for standard cases it is not needed, and you should simply use the namespace. However, keep in mind that under the hood the namespace is nothing more than syntactic sugar. At the end of the day, you still end up with standard Spring beans and objects. Spring 2.0 introduced support for defining custom XML namespaces. Since then, a lot of projects have made use of this facility, making them more attractive to work with. An XML custom namespace is simply an XML-based Domain Specific Language (DSL), guided by the rules of an XML schema (.xsd) file that allows developers to create Spring beans using concepts and a syntax more in synch with the programming concerns they are trying to model.
81
Chapter 4
Spring Security Architecture and Design
Note A DSL is a language customized to represent the concepts of a particular application domain. Sometimes, a whole new language is created to support the new domain, which is referred to as an external DSL. Some other times, an existing language is tweaked to allow for new expressions that represent the concepts of the domain, and this is referred to as an internal DSL. In the case presented in this chapter, you are using a general-purpose language (XML); however, you are defining certain constraints about the elements (using XSD) and thus are creating an internal DSL to represent security concepts. Making Spring aware of a new namespace is really simple. (That’s not to say it is simple to actually parse the information of the XML and convert it to beans—this depends on the complexity of your DSL.) All you need is the following: •
An .xsd file defining your particular XML structure
•
A spring.schemas file where you specify the mapping between a URL-based schema location and the location of your .xsd file in your classpath
•
A spring.handlers file where you specify which class is in charge of handling everything related to your namespace
•
A bunch of parser classes that will be in charge of parsing each of the top elements defined in your XML file
In Chapter 8, you will see some examples of how to create a new namespace element and integrate it with Spring Security. For Spring Security, all the namespace configuration-related information resides in the config module. In Figure 4-7, you can see the expanded structure of the config module as seen in the Eclipse integrated development environment (IDE), in this case Spring tool Suite v4 for Eclipse.
82
Chapter 4
Spring Security Architecture and Design
Figure 4-7. Spring Security’s file structure The files spring.handlers and spring.schemas should reside in the META-INF directory in the classpath so that Spring can find them there. OK, so enough of the general namespace information. More specifically, how does the Spring Security namespace work? When you create a Spring-based application using XML-defined application context configuration with some of the Spring Security namespace definitions, and you run the application, when it starts to load up, it looks in the application context’s namespace definitions at the top of the XML configuration file. It will find the reference to the
83
Chapter 4
Spring Security Architecture and Design
Spring Security namespace (normally a reference like xmlns:security="http://www. springframework.org/schema/security"). Using the information from the spring. handlers mapping file, it will see that the file to handle the security elements is the final class, org.springframework.security.config.SecurityNamespaceHandler. Spring calls the parse method of this class for every top element in the configuration file that uses the security namespace. Figure 4-8 shows the load-up sequence for this process.
Figure 4-8. Sequence of loading up a Spring namespace SecurityNamespaceHandler delegates to a series of BeanDefinitionParser objects for the individual parsing of each top-level element. The whole list of elements supported in the Spring Security namespace configuration are defined in the class org.springframework.security.config.Elements as constants. This class is shown in Listing 4-2.
84
Chapter 4
Spring Security Architecture and Design
Listing 4-2. Constants for All the Spring Security Namespace Elements package org.springframework.security.config; public abstract class Elements { public static final String ACCESS_DENIED_HANDLER = "access-denied-handler"; public static final String AUTHENTICATION_MANAGER = "authentication-manager"; public static final String AFTER_INVOCATION_PROVIDER = "after-invocation-provider"; public static final String USER_SERVICE = "user-service"; public static final String JDBC_USER_SERVICE = "jdbc-user-service"; public static final String FILTER_CHAIN_MAP = "filter-chain-map"; public static final String INTERCEPT_METHODS = "intercept-methods"; public static final String INTERCEPT_URL = "intercept-url"; public static final String AUTHENTICATION_PROVIDER = "authentication-provider"; public static final String HTTP = "http"; public static final String LDAP_PROVIDER = "ldap-authentication-provider"; public static final String LDAP_SERVER = "ldap-server"; public static final String LDAP_USER_SERVICE = "ldap-user-service"; public static final String PROTECT_POINTCUT = "protect-pointcut"; public static final String EXPRESSION_HANDLER = "expression-handler"; public static final String INVOCATION_HANDLING = "pre-post-annotation-handling"; public static final String INVOCATION_ATTRIBUTE_FACTORY = "invocation- attribute-factory"; public static final String PRE_INVOCATION_ADVICE = "pre-invocation-advice"; public static final String POST_INVOCATION_ADVICE = "post-invocation-advice"; public static final String PROTECT = "protect"; public static final String SESSION_MANAGEMENT = "session-management"; public static final String CONCURRENT_SESSIONS = "concurrency-control"; public static final String LOGOUT = "logout"; public static final String FORM_LOGIN = "form-login"; public static final String OPENID_LOGIN = "openid-login"; public static final String OPENID_ATTRIBUTE_EXCHANGE = "attribute-exchange"; public static final String OPENID_ATTRIBUTE = "openid-attribute"; public static final String BASIC_AUTH = "http-basic"; public static final String REMEMBER_ME = "remember-me"; public static final String ANONYMOUS = "anonymous"; 85
Chapter 4
Spring Security Architecture and Design
public static final String FILTER_CHAIN = "filter-chain"; public static final String GLOBAL_METHOD_SECURITY = "global-method-security"; public static final String PASSWORD_ENCODER = "password-encoder"; public static final String SALT_SOURCE = "salt-source"; public static final String PORT_MAPPINGS = "port-mappings"; public static final String PORT_MAPPING = "port-mapping"; public static final String CUSTOM_FILTER = "custom-filter"; public static final String REQUEST_CACHE = "request-cache"; public static final String X509 = "x509"; public static final String JEE = "jee"; public static final String FILTER_SECURITY_METADATA_SOURCE = "filtersecurity-metadata-source"; public static final String METHOD_SECURITY_METADATA_SOURCE = "methodsecurity-metadata-source"; @Deprecated public static final String FILTER_INVOCATION_DEFINITION_SOURCE = "filterinvocation-definition-source"; public static final String LDAP_PASSWORD_COMPARE = "password-compare"; public static final String DEBUG = "debug"; public static final String HTTP_FIREWALL = "http-firewall"; } From the list of elements presented in the previous class, the top-level ones as used in the XML configuration files are as follows (in the previous listing, we refer to them by the name of the constant and not by the XML element name):
86
•
LDAP_PROVIDER: This element is used to configure the Lightweight Directory Access Protocol (LDAP) authentication provider for your application in case you require one.
•
LDAP_SERVER: This element is used to configure an LDAP server in your application.
•
LDAP_USER_SERVICE: This element configures the service for retrieving user details from an LDAP server and populating that user’s authorities (Spring Security uses the term “authorities” to refer to the permission names that are granted to a particular user. For example, ROLE_USER is an authority).
Chapter 4
Spring Security Architecture and Design
•
USER_SERVICE: This element defines the in-memory user service where you can store user names, credentials, and authorities directly in the application context definition file. Notice that this type of configuration is specific for test environments and academic purposes because it is easy to set up and fast.
•
JDBC_USER_SERVICE: This element allows you to set up a database- driven user service, where you specify a DataSource and the queries to retrieve the user information from a database.
•
AUTHENTICATION_PROVIDER: This element defines a DaoAuthenticationProvider, which is an authentication provider that delegates to an instance of UserDetailsService. The UserDetailsService can be any of the ones defined in the previous three bullet points, or a reference to a customized one.
•
GLOBAL_METHOD_SECURITY: This element is in charge of setting up the global support in your application to the annotations @Secured, @javax.annotation.security.RolesAllowed, @PreAuthorize, and @PostAuthorize. This element is the one that will handle the registration of a method interceptor that will be aware of all the metadata of the bean’s methods in order to apply the corresponding security advice.
•
AUTHENTICATION_MANAGER: This element registers a global ProviderManager in the application and sets up the configured AuthenticationProviders on it.
•
METHOD_SECURITY_METADATA_SOURCE: This element registers a MapBasedMethodSecurityMetadataSource in the application context. It will hold a Map>. It does this so that when a request is made to a method, the method can be retrieved and its security constraints can be checked.
•
DEBUG: For development purposes, this element registers a DebugFilter in the security filter chain.
87
Chapter 4
Spring Security Architecture and Design
•
HTTP: This is the main element for a web-based secure application. The HTTP element is really powerful. It allows for the definition of URL-based security-mapping strategies, the configuration of the filters, the Secure Sockets Layer (SSL) support, and other HTTP- related security configurations.
•
HTTP_FIREWALL: This element uses a firewall element and adds it to the filter chain if it is configured. The firewall referenced should be an implementation of Spring’s own HttpFirewall interface.
•
FILTER_INVOCATION_DEFINITION_SOURCE: This element has been deprecated. See the following one.
•
FILTER_SECURITY_METADATA_SOURCE: This element wraps a list of elements. These elements map the relationship between URLs and the ConfigAttributes required for accessing those URLs.
•
FILTER_CHAIN: This element allows you to configure the Spring Security filter chain that will be used in the application, which filters you want to add to the chain, and a request matcher if you want to customize how the chain matches requests. The most important request matchers are ant-based and regexp-based.
You will be using the Spring Security namespace thoroughly throughout the book, so many of the elements described here will be revisited in later chapters.
The Filters and Filter Chain The filter chain model is what Spring Security uses to secure web applications. This model is built on top of the standard servlet filter functionality. Working as an Intercepting Filter Pattern, the filter chain in Spring Security is built of a few single- responsibility filters that cover all the different security constraints required by the application. The filter chain in Spring Security preprocesses and postprocesses all the HTTP requests that are sent to the application and then applies security to URLs that require it. A typical filter for a single HTTP request is shown in Figure 4-9.
88
Chapter 4
Spring Security Architecture and Design
Figure 4-9. Spring Security filter example for a single HTTP request The Spring Security filter chain is made up of Spring beans; however, standard servlet-based web applications don’t know about Spring beans. From the point of view of the container, Spring Security is actually a single filter, which internally contains a lot of filters with different purposes. Spring Security is installed as a single filter in the chain named FilterChainProxy, which is a chain of filters containing all the security needed, as shown in Figure 4-10.
Figure 4-10. Spring Security filters overview
89
Chapter 4
Spring Security Architecture and Design
In the security filter, there is also a special layer of indirection installed in the container named DelegatingFilterProxy. DelegatingFilterProxy does not need to be a Spring Bean. The flow works so that the DelegatingFilterProxy filter will delegate to a FilterChainProxy, which instead is always a bean with a fixed name of springSecurityFilterChain which at the end will be responsible, within your application, for •
Protecting the application URLs
•
Validating the submitted username and passwords
•
Redirecting to the login form
The DelegatingFilterProxy process containing the FilterChainProxy is shown in Figure 4-11.
Figure 4-11. Spring Security filter chain overview The Spring Security filter configuration is achieved via a special servet and two main XML files, web.xml and applicationContext.xml. Please notice that starting with Servlet 3.0, web.xml is no longer necessary. This special servlet filter is needed to cross the boundaries between the standard servlet API and life cycle and the Spring application where the bean filters will reside. This is the job of the org.springframework.web.filter.DelegatingFilterProxy defined in the web.xml, which uses under the hood the WebApplicationContextUtils. getWebApplicationContext utility method to retrieve the root application context of the application. These two classes are from the Spring Framework, not from Spring Security. 90
Chapter 4
Spring Security Architecture and Design
Figure 4-12 shows the configuration of the filter chain.
Figure 4-12. Understanding the Spring Security filter configuration. The filter in the web.xml file has the same name as the bean in the Spring application context so that the listener can find it Full information about how migrating from Spring Security 3.x to 4.x and from 4.x to 5.x (Java Configuration) can be found at https://docs.spring.io/spring-security/ site/migrate/current/3-to-4/html5/migrate-3-to-4-jc.html and https://github. com/spring-projects/spring-security/issues/4874. The filter chain will be fully explained in Chapter 5. However, here we’ll provide an overview of which filters are available and what they do. The available filters are defined as enums in the file named org.springframework.security.config.http. SecurityFilters. The enums are then referenced later in the startup process when instantiating the bean definitions for each filter. Here are the defined filters: •
CHANNEL_FILTER: This filter ensures that the request is handled by the correct channel—meaning, in most cases, it determines whether or not the request is handled by HTTPS.
•
CONCURRENT_SESSION_FILTER: This filter is part of the concurrent session-handling mechanism. Its main function is to query the session to see if it has expired (which happens mainly when the maximum number of concurrent sessions per user are reached) and to log out the user if that is the case.
•
SECURITY_CONTEXT_FILTER: This filter populates SecurityContextHolder with a new or existing security context to be used by the rest of the framework. 91
Chapter 4
92
Spring Security Architecture and Design
•
LOGOUT_FILTER: This filter is based, by default, on a particular URL invocation (/logout). It takes care of the logout process, including tasks such as clearing the cookies, removing the “remember me” information, and clearing the security context.
•
X509_FILTER: This filter extracts the principal and credentials from an X509 certificate using the class java.security.cert. X509Certificate and attempts to authenticate with these preauthenticated values.
•
PRE_AUTH_FILTER: This filter is used with the J2EE authentication mechanism. The J2EE authenticated principal will be used as the preauthenticated principal in the framework.
•
FORM_LOGIN_FILTER: This filter is used when a user name and password is required on a login form. This filter takes care of authenticating with the requested user name and password. It handles, from Spring v4, requests to a particular URL (/login) with a particular set of user-name and password parameters (username, password).
•
OPENID_FILTER: This filter processes OpenId authentication requests, handling both the initial request with the OpenId identity to the external server and the redirect from the OpenId server back to the application. All this interaction is managed when the filter detects requests to the preconfigured URL /openid since Spring v4.
•
LOGIN_PAGE_FILTER: This filter generates a default login page when the user doesn’t provide a custom one. It is activated when the URL / spring_security_login is requested.
•
DIGEST_AUTH_FILTER: This filter processes HTTP Digest authentication headers. It looks for the presence of both Digest and Authorization HTTP request headers. It can be used to provide Digest authentication to standard user agents, like browsers, or to application clients like SOAP. On successful authentication, the SecurityContext will be populated with the valid Authentication object.
Chapter 4
Spring Security Architecture and Design
•
BASIC_AUTH_FILTER: This filter processes the BASIC authentication headers in an HTTP request. It looks for the header Authorization and tries to authenticate with these credentials.
•
REQUEST_CACHE_FILTER: This filter retrieves a request from the request-cache that matches the current request, and it sends the cached one through the rest of the filter chain.
•
SERVLET_API_SUPPORT_FILTER.: This filter wraps the request in a request wrapper that implements the Servlet API security methods, like isUserInRole, and delegates it to SecurityContextHolder. This allows for the convenient use of the request object itself to get the security information. For example, you can use request. getAuthentication to retrieve the Authentication object.
•
JAAS_API_SUPPORT_FILTER: This filter tries to obtain and use javax. security.auth.Subject, which is a final class, and continue the filter chain execution with this subject.
•
REMEMBER_ME_FILTER: If no user is logged in, this filter will look to see whether there is any “remember me” functionality active and any “remember me” Authentication available. If there is, this filter will try to login automatically and authenticate with this “remember me” information.
•
ANONYMOUS_FILTER: This filter checks to see whether there is already an Authentication in the context. If there is not, it creates a new Anonymous one and sets it on the security context.
•
SESSION_MANAGEMENT_FILTER: This filter passes the Authentication object that corresponds to the authenticated user who is logged in to the system to some configured session management processors in order to do session-related handling of the Authentication. Mainly, these processors will do some kind of validation and throw SessionAuthenticationException if appropriate. Currently, these processors (or strategies) include only one main class in the form of org.springframework.security.web.authentication.session. ConcurrentSessionControlStrategy, dealing with both session fixation and concurrent sessions. 93
Chapter 4
Spring Security Architecture and Design
•
EXCEPTION_TRANSLATION_FILTER: This filter handles the translation between Spring Security exceptions (like AccessDeniedException) and the corresponding HTTP status code. It also redirects to the application entry point in case the exception is thrown because there is not yet an authenticated user in the system.
•
FILTER_SECURITY_INTERCEPTOR: This filter handles the authorization mechanism for defined URLs. It delegates to its parent class’ (AbstractSecurityInterceptor) functionality (which we’ll cover later in the chapter) the actual workflow logic of granting or not granting the access to the specific resource.
•
SWITCH_USER_FILTER: This filter allows a user to impersonate another one by visiting a particular URL, which since Spring v4 was updated from from /j_spring_security_switch_user to /login/ impersonate. This URL should be secured to allow just certain users access to this functionality. Also, the method attemptSwitchUser in the implementing class SwitchUserFilter can be overridden to add constraints, so that you can use more finely grained information to decide if certain users are allowed or not allowed to impersonate other users.
C onfigAttribute The interface org.springframework.security.access.ConfigAttribute encapsulates the access information metadata present in a secured resource. For example, for your study purposes, ROLE_ADMIN is a ConfigAttribute. There are a few implementations of ConfigAttribute, as you can see in Figure 4-13.
94
Chapter 4
Spring Security Architecture and Design
Figure 4-13. ConfiAttribute hierarchy When you annotate a method with @Secured("ROLE_ADMIN") or something similar, or specify a URL with , Spring Security does the following. On startup, as normal Spring functionality, all the bean postprocessors in the ApplicationContext get invoked. And in the case of Spring Security, the process is the following. What happens in the case of web requests is not really that complex. Web requests don’t really use the postprocessor infrastructure. When you use the element , Spring Security uses the class FilterInvocationSecurityMetadataSource Parser to parse this XML. In the parsing process, the private method parseInterceptUrls ForFilterInvocationRequestMap will be invoked. This method maps the information contained in each of the URL patterns in the XML element into a map of Ant-style request paths, like /*, ROLE_USER. Here /* is an Ant pattern and ROLE_USER is a config attribute (this says that basically this config attribute is needed to access any URL with this pattern). This map, ultimately, will be set up in an instance of an implementation of the interface org.springframework.security.web.access.intercept. FilterInvocationSecurityMetadataSource inside the FilterSecurityInterceptor, which uses it when each request comes to match the requested URL against the keys in the map to find out if the URL is secured and then extracts the ConfigAttributes against which to check the authorities of the requesting Authentication object. This setup process is shown in Figure 4-14.
95
Chapter 4
Spring Security Architecture and Design
Figure 4-14. ConfigAttribute setup for web applications For method-level security, you have many options—the most common one being the configurations performed through the use of annotations. There are a few different annotations available in the framework; however, the setup treatment by the framework is very similar. In the case of the @Secured annotation, for instance, you need to make Spring aware that this special annotation needs a special security treatment. To do that, you register the following in the security application context XML file: When you set up that definition in the application context XML configuration, Spring Security creates a new MethodSecurityMetadataSourceAdvisor and registers it in the application context. This advisor is marked as an infrastructure advisor and is picked up by Spring Core’s InfrastructureAdvisorAutoProxyCreator, which is a BeanPostProcessor that Spring initializes automatically. It processes all the beans in the application context and determines if any of the configured advisors can be applied to any of the beans and their methods. If so, it wraps the bean with the required advisor or advisors. The postprocessor finds the 96
Chapter 4
Spring Security Architecture and Design
MethodSecurityMetadataSourceAdvisor and eventually calls an implementation of the MethodSecurityMetadataSource.getAttributes method for each bean and all its methods, to determine if they have any ConfigAttribute configured as metadata in them. If the MethodSecurityMetadataSource finds ConfigAttributes in the bean, the InfrastructureAdvisorAutoProxyCreator, from Spring Core, calls its own method (createProxy) to apply MethodSecurityMetadataSourceAdvisor, which internally contains the Security Interceptor as the org.aopalliance.aop.Advice to apply to the bean. Figure 4-15 shows this interaction graphically.
Figure 4-15. Decorating annotated beans with security advice At first sight, this looks like simple magic, but it takes a lot of hard work from Spring to do it. And you have to thank the Spring and Spring Security developers for taking care of all this and giving you a simple and powerful API for resolving your security concerns.
The Authentication Object The Authentication object is an abstraction that represents the entity that logs in to the system—most likely, a user. Because it is normally a user authenticating, we’ll assume so and use the term “user” in the rest of the book. There are a few implementations of the Authentication object in the framework, as you can see in Figure 4-16.
97
Chapter 4
Spring Security Architecture and Design
Figure 4-16. Authentication hierarchy An Authentication object is used both when an authentication request is created (when a user logs in), to carry around the different layers and classes of the framework the requesting data, and then when it is validated, containing the authenticated entity and storing it in SecurityContext. The most common behavior is that when you log in to the application, a new Authentication object will be created, storing your user name, password, and permissions, which are technically known as Principal, Credentials, and Authorities, respectively. Authentication is an interface, and it is pretty simple, as Listing 4-3 shows.
Note There are many implementations of the Authentication interface, and in this book we will refer most of the time to the general Authentication interface when we are not interested in the particular implementation type. Of course, when we need to talk about the specifics of an implementation detail, we will refer to the concrete classes. Listing 4-3. The Authentication Interface package org.springframework.security.core; import java.io.Serializable; import java.security.Principal; import java.util.Collection;
98
Chapter 4
Spring Security Architecture and Design
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.context.SecurityContextHolder; public interface Authentication extends Principal, Serializable { Collection extends GrantedAuthority> getAuthorities(); Object getCredentials(); Object getDetails(); Object getPrincipal(); Boolean isAuthenticated(); Void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException; } As Figure 4-16 shows, currently there are a few implementations of Authentication in the framework: •
UsernamePasswordAuthenticationToken: This is a simple implementation that contains, as its name clearly specifies, the user name and password information of the authenticated (or pending authentication) user. It is the most common Authentication implementation used throughout the system, as many of the AuthenticationProvider objects depend directly on this class.
•
PreAuthenticatedAuthenticationToken: This implementation exists for handling preauthenticated Authentication objects. Preauthenticated authentications are those where the actual authentication process is handled by an external system, and Spring Security deals only with extracting the principal (or user) information out of the external system’s messages.
•
OpenIDAuthenticationToken: This is an Authentication implementation used specifically for OpenID authentication schemes. It is used by both the OpenID filter and the OpenID authentication provider.
•
RunAsUserToken: This implementation is used by the RunAsManager, which is called by the Security Interceptor when the accessed resource contains a ConfigAttribute that starts with the prefix RUN_AS_. If there is a ConfigAttribute with this value, RunAsManager adds new GrantedAuthorities to the authenticated user corresponding to the RUN_AS value. 99
Chapter 4
Spring Security Architecture and Design
SecurityContext and SecurityContextHolder The interface org.springframework.security.core.context.SecurityContext (actually, its implementation is SecurityContextImpl) is the place where Spring Security stores the valid Authentication object, associating it with the current thread. The org.springframework.security.core.context.SecurityContextHolder is the class used to access SecurityContext from many parts of the framework. It is built mainly of static methods to store and access SecurityContext, delegating to configurable strategies the way to handle this SecurityContext—for example, one SecurityContext per thread (default), one global SecurityContext, or a custom strategy. The class diagram for these classes can be seen in Figure 4-17, and Listings 4-4 and 4-5 show the two classes.
Notice that the entire SecurityContextHolder reference can be found on GigHub at https://github.com/spring-projects/spring-security/blob/master/core/src/ main/java/org/springframework/security/core/context/SecurityContext.java.
Listing 4-5. SecurityContextHolder Class package org.springframework.security.core.context; import org.springframework.util.ReflectionUtils; import java.lang.reflect.Constructor; public class SecurityContextHolder { public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL"; public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL"; public static final String MODE_GLOBAL = "MODE_GLOBAL"; public static final String SYSTEM_PROPERTY = "spring.security.strategy"; private static String strategyName = System.getProperty(SYSTEM_PROPERTY); private static SecurityContextHolderStrategy strategy; private static int initializeCount = 0; static { initialize(); } public static void clearContext() { strategy.clearContext(); } public static SecurityContext getContext() { return strategy.getContext(); } public static int getInitializeCount() { return initializeCount; } private static void initialize() { if ((strategyName == null) || "".equals(strategyName)) { strategyName = MODE_THREADLOCAL; } 101
Chapter 4
Spring Security Architecture and Design
if (strategyName.equals(MODE_THREADLOCAL)) { strategy = new ThreadLocalSecurityContextHolderStrategy(); } else if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) { strategy = new InheritableThreadLocalSecurityContextHolderStrategy(); } else if (strategyName.equals(MODE_GLOBAL)) { strategy = new GlobalSecurityContextHolderStrategy(); } else { try { Class> clazz = Class.forName(strategyName); Constructor> customStrategy = clazz.getConstructor(); strategy = (SecurityContextHolderStrategy) customStrategy.newInstance(); } catch (Exception ex) { ReflectionUtils.handleReflectionException(ex); } } initializeCount++; } public static void setContext(SecurityContext context) { strategy.setContext(context); } public static void setStrategyName(String strategyName) { SecurityContextHolder.strategyName = strategyName; initialize(); } public static SecurityContextHolderStrategy getContextHolderStrategy() { return strategy; } public static SecurityContext createEmptyContext() { return strategy.createEmptyContext(); } public String toString() { return "SecurityContextHolder[strategy='" + strategyName + "'; initializeCount=" + initializeCount + "]"; } } 102
Chapter 4
Spring Security Architecture and Design
A uthenticationProvider AuthenticationProvider is the main entry point for authenticating an Authentication object. This interface has only two methods, as Listing 4-6 shows. This is one of the major extension points of the framework, as you can tell by the many classes that currently extend this interface. Each of the implementing classes deals with a particular external provider to authenticate against. So if you come across a particular provider that is not supported and you need to authenticate against it, you probably need to implement this interface with the required functionality. You will see examples of this later in the book. AuthenticationProvider (see Figure 4-18) is very similar to AuthenticationManager but it has an extra method which can be used to call a query if it supports a given Authentication type, as shown here: public interface AuthenticationProvider { Authentication authenticate(Authentication authentication) throws AuthenticationException; boolean supports(Class> authentication); } Here are some of the existing providers that come with the framework: CasAuthenticationProvider JaasAuthenticationProvider DaoAuthenticationProvider OpenIDAuthenticationProvider RememberMeAuthenticationProvider LdapAuthenticationProvider
103
Chapter 4
Spring Security Architecture and Design
Figure 4-18. AuthenticationProvider hierarchy The entire AuthenticationProvider reference can be found on GitHub at https:// github.com/spring-projects/spring-security/blob/master/core/src/main/java/ org/springframework/security/authentication/AuthenticationProvider.java.
AccessDecisionManager AccessDecisionManager is the class in charge of deciding if a particular Authentication object is allowed or not allowed to access a particular resource. In its main implementations, it delegates to AccessDecisionVoter objects, which basically compare the GrantedAuthorities in the Authentication object against the ConfigAttribute(s) required by the resource that is being accessed, deciding whether or not access should be granted. They emit their vote to allow access or not. The AccessDecisionManager implementations take the output from the voters into consideration and apply a determined strategy on whether or not to grant access. Voters, however, also can abstain from voting. The AccessDecisionManager interface can be seen in Listing 4-7. Its UML class diagram is shown in Figure 4-19.
Figure 4-19. AccessDecisionManager hierarchy The entire AccessDecisionManager reference can be found on GitHub at https:// github.com/spring-projects/spring-security/blob/master/core/src/main/java/ org/springframework/security/access/AccessDecisionManager.java.
public interface AccessDecisionManager { void decide(Authentication authentication, Object object, Collection configAttributes) throws AccessDeniedException, InsufficientAuthenticationException; boolean supports(ConfigAttribute attribute); boolean supports(Class> clazz); } The current AccessDecisionManager implementations all delegate to voters, but they work in slightly different ways. The current voters, which are described in the following sections, are defined in the package org.springframework.security.access.vote.
AffirmativeBased This access decision manager calls all its configured voters, and if any of them vote that access should be granted, this is enough for the access decision manager to allow access to the secured resource. If no voters vote to grant access and there is at least one who votes not to grant it, the access decision manager throws an AccessDeniedException denying access. If there are only abstaining voters, a decision is made based on the AccessDecisionManager’s instance variable allowIfAllAbstainDecisions, which is a Boolean that defaults to false, determining if access should be granted or not when all voters abstain.
ConsensusBased This access decision manager implementation calls all its configured voters to make a decision to either grant or deny access to a resource. The difference with the AffirmativeBased decision manager is that the ConsensusBased decision manager decides to grant access only if there are more voters granting access than voters denying it. So the majority wins in this case. If there are the same number of granting voters as denying voters, the value of the instance variable allowIfEqualGrantedDeniedDecisions is used to decide. By default, this variable’s value is true, and access is granted. When all voters abstain, the access decision will be decided the same way as it is for the AffirmativeBased manager.
106
Chapter 4
Spring Security Architecture and Design
UnanimousBased As you probably guessed, this access decision manager will grant access to the resource only if all the configured voters vote in favor of allowing access to the resource. If any voter votes to deny the access, the AccessDeniedException will be thrown. The “all abstain” case is handled the same way as with the other implementations of AccessDecisionManager.
A ccessDecisionVoter This discussion of the AccessDecisionManager and its current implementations should have made clear the importance of the Access Decision Voters, because they are the ones, working as a team, who ultimately determine if a particular Authentication object has enough privileges to access a particular resource. The org.springframework.security.access.AccessDecisionVoter interface is very simple as well, and you can see it in Listing 4-8. The main method is vote, and as can be deduced from the interface, it will return one of three possible responses (ACCESS_GRANTED, ACCES_ABSTAIN, ACCESS_DENIED), depending on whether the required conditions are satisfied. The satisfaction or not of the conditions is given by analyzing the Authentication object’s rights against the required resource. In practice, this basically means that the Authentication’s authorities are compared against the resource’s security attributes looking for matches. The following are the current AccessDecisionVoter implementations: •
org.springframework.security.access.annotation.Jsr250Voter: This voter votes on resources that are secured with JSR 250 annotations—namely, DenyAll, PermitAll, and RolesAllowed. Their names are very descriptive. DenyAll won’t allow any access at all to the resource, independent of the security information carried by the Authentication object trying to access it. PermitAll will allow access to everyone, regardless of what roles they have. The RolesAllowed annotation can be configured with a series of roles. If an Authentication object tries to access the resource, it must have one of the roles configured in the RolesAllowed annotation in order to get access granted by this voter.
107
Chapter 4
108
Spring Security Architecture and Design
•
org.springframework.security.access.prepost. PreInvocationAuthorizationAdviceVoter: This voter votes on resources with expression configurations based on @PreFilter and @PreAuthorize annotations. @PreFilter and @PreAuthorize annotations support a value attribute that can have a SpEL expression. The PreInvocationAuthorizationAdviceVoter is the one in charge of evaluating the SpEL expressions (of course with the help of Spring’s SpEL evaluation mechanism) provided in these annotations. We will be explaining and using SpEL expressions in several parts of the book so this concept will become clearer as the book advances.
•
org.springframework.security.access.vote.AbstractAclVoter: This is the abstract class that has the skeleton to write voters dealing with domain ACL rules so that other implementing class builds on its functionality to add voting behavior. Currently, it is implemented in AclEntryVoter, which votes on users’ permissions on domain objects. This voter will be covered in the chapter dedicated to ACL.
•
org.springframework.security.access.vote. AuthenticatedVoter: This voter votes whenever a ConfigAttribute referencing any of the three possible levels of authentication is present on the secured resource. The three levels are IS_ AUTHENTICATED_FULLY, IS_AUTHENTICATED_REMEMBERED, and IS_AUTHENTICATED_ANONYMOUSLY. The voter emits a positive vote if the Authentication object’s authentication level matches (or is a stronger level in the hierarchy IS_AUTHENTICATED_FULLY > IS_ AUTHENTICATED_REMEMBERED > IS_AUTHENTICATED_ANONYMOUSLY) the authentication level configured in the resource.
•
org.springframework.security.access.vote.RoleVoter: This is perhaps the most commonly used voter of them all. This voter, by default, is able to vote on resources that have ConfigAttribute(s) containing security metadata starting with the prefix ROLE_ (which can be overridden). When an Authentication object tries to access the resource, its GrantedAuthorities are matched against the relevant ConfigAttributes. If there is a match, access is granted. If there isn’t, access is denied.
Chapter 4
•
Spring Security Architecture and Design
org.springframework.security.access.expression. WebExpressionVoter: This is the voter in charge of evaluating SpEL expressions in the context of web requests in the filter chain— expressions like 'hasRole' in the element. To make use of this voter, and in general to support SpEL expressions in web security, the use-expressions="true" attribute needs to be added to the element.
The voters model is yet another one in the framework that is open for extension and customization. You can easily create your own implementation and add it to the framework. You will see how to do this in Chapter 8. The entire AccessDecisionVoter reference can be found on GitHub at https:// github.com/spring-projects/spring-security/blob/master/core/src/main/java/ org/springframework/security/access/AccessDecisionVoter.java.
Listing. 4-8. AccessDecisionVoter Interface package org.springframework.security.access; import java.util.Collection; import org.springframework.security.core.Authentication; public interface AccessDecisionVoter { int ACCESS_GRANTED = 1; int ACCESS_ABSTAIN = 0; int ACCESS_DENIED = -1; boolean supports(ConfigAttribute attribute); boolean supports(Class> clazz); int vote(Authentication authentication, S object, Collection attributes); }
U serDetailsService and AuthenticationUserDetailsService The interface org.springframework.security.core.userdetails. UserDetailsService is in charge of loading the user information from the underlying user store (in-memory, database, and so on) when an authentication request arrives in
109
Chapter 4
Spring Security Architecture and Design
the application. UserDetailsService makes use of the provided user name for looking up the rest of the required user data from the datastore. It defines just one method, as you can see in Listing 4-9. You can see its hierarchy in Figure 4-20.
Figure 4-20. UserDetailsService hierarchy The interface org.springframework.security.core.userdetails. AuthenticationUserDetailsService is more generic. It allows you to retrieve a UserDetails using an Authentication object instead of a user name String, making it more flexible to implement. Actually, there is an implementation of AuthenticationUserDetailsService (UserDetailsByNameServiceWrapper) that simply delegates to a UserDetailsService extracting the user name from the Authentication object. Listing 4-10 shows the AuthenticationUserDetailsService interface. These are the two main strategies (AuthenticationUserDetailsService and UserDetailsService) used for retrieving the user information when attempting authentication. They are usually called from the particular AuthenticationProvider that is being used in the application. For example, the OpenIDAuthenticationProvider and CasAuthenticationProvider delegate to an AuthenticationUserDetailsService to
110
Chapter 4
Spring Security Architecture and Design
obtain the user details, while the DaoAuthenticationProvider delegates directly to a UserDetailsService. Some other providers don’t use a user details service of any kind (for example, JaasAuthenticationProvider uses its own mechanism to retrieve the principal from a javax.security.auth.login.LoginContext), and some others use a completely custom one (for example, LdapAuthenticationProvider uses a UserDetailsContextMapper).
U serDetails The interface org.springframework.security.core.userdetails.UserDetails object is the main abstraction in the system, and it’s used to represent a full user in the context of Spring Security. It is also made available to be accessed later in the system from any point that has access to SecurityContext. Normally, developers create their own implementation of this interface to store particular user details they need or want (like email, telephone, address, and so on). Later, they can access this information, which will be encapsulated in the Authentication object, and they can be obtained by calling the getPrincipal method on it. Some of the current UserDetailsService (for example, InMemoryUserDetailsManager) implementations use the class org.springframework. security.core.userdetails.User, which is available in the core of the framework, as the UserDetails implementation returned by the method loadUserByUsername. However, this is another of those configurable points of the framework, and you can easily create your own UserDetails implementation and use that in your application. Listing 4-11 shows the UserDetails interface.
A CL The ACL is the module in charge of securing your application at the individual domain object level with a fine level of granularity. This means, in a general way, assigning an ID to each domain object in your application and creating a relationship between these objects and the different users of the application. These relationships determine whether or not a determined user is allowed access to a particular domain. The ACL model offers a fine-grained, access-level configuration you can use to define different rules for accessing the objects depending on who is trying to access it. (For example, a user might be allowed read access while another user will have write/read access over the same domain object.) The current support for ACLs is configured to get the configuration rules from a relational database. The DDL (Data Definition Language) for configuring the database comes along with the framework itself, and it can be found in the ACL module. ACL security will be fully covered in Chapter 7.
112
Chapter 4
Spring Security Architecture and Design
J SP Taglib If you are working to secure a Java web application, the taglib component of the framework is the one you use to hide or show certain elements in your pages according to your users’ permissions. The tags are simple to use and, at the same time, very convenient for making a more usable web site. They help you adapt the UI of your application on a per-user (or more commonly, per-role) basis.
Good Design and Patterns in Spring Security We said it before, but we will repeat it here: One of the great aspects of working with open source software is that you can (and we say you should) look at the source code and understand the software at a new, deeper level. Also, you can look at the way the software is built, what is good, and what is bad (at least by your own subjective standards) and just learn how other developers work. This can have a great impact on the way you work, because you might discover a way of doing things that you couldn’t have learned on your own. The code for Spring Security is publicly available on GitHub at https://github.com/ spring-projects/spring-security. Sometimes, of course, you will find things you don’t like, but that is good as well. You can learn from other people’s mistakes as much as you can learn from their successes. For us, Spring in general and Spring Security in particular have achieved something that we found invaluable in the Java development space—that is, they can make us better developers even without us noticing it. For instance, we often ask ourselves, “How many people would be using a template pattern for accessing databases if they weren’t using Spring, instead of a more awkward DB access layer?” or “How many people would be just programming against implementation classes all the time, creating unnecessary coupling if it wasn’t for Spring’s DI support?” or “How many people would have cross- cutting concerns, like transactions, all over their code base if it wasn’t for how easily Spring brings AOP into the development process?” We think helping good practices almost without noticing is really a great achievement of Spring. It won’t create great developers by itself for sure, but it helps the average developer to not make mistakes that they might make if they didn’t have the support of the framework and its principles to adhere to. 113
Chapter 4
Spring Security Architecture and Design
As you might see from the description of the main components of the framework, Spring Security itself is built with good design principles and patterns in mind. Let’s have a brief look here at some of the things we find interesting in the framework, and from which you can learn. This section won’t really help you to do more with Spring Security, but it will serve as a way to appreciate the good work that has been done in constructing this fantastic framework.
S trategy Pattern A big part of the pluggability and modularity of the framework is achieved thanks to the wide use of the Strategy pattern. You can find it, for example, in the type of SecurityContext to be used, the AuthenticationProvider hierarchy, the AccessDecisionVoters, and many other elements. Covering design patterns is outside the scope of this book but as a reminder of the strategy pattern’s power, we leave you with this definition from Wikipedia: “The strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.” That definition shows a great deal of the power that comes with working with interfaces. You can have different implementations of the same interface and pass any of them to a client class for doing different kinds of work. The client classes don’t need to know or care about the implementation details they are working with. Knowing the interface or contract is enough to leverage its job.
D ecorator Pattern Built into Spring’s core AOP framework, you can find the Decorator pattern—mostly in the way that your annotated business classes and methods get security constraints applied to them. Basically, your objects have only certain meta information related to the security constraints that should be applied to them, and then by some “Spring magic” they get wrapped with security handling. Listing 4-12 shows the invoke method of MethodSecurityInterceptor. In the listing, you can see how the objects are decorated with prefunctionality and postfunctionality that surrounds the invocation of the actual method.
SRP Spring Security’s code seems to take very seriously the Single Responsibility Principle. There are many examples of it around the framework, because any object you choose seems to have one and only one responsibility. For example, the AuthenticationProvider deals only with the general concern of authenticating a principal with its credentials in the system. The SecurityInterceptor is simply in charge of intercepting the requests, and it delegates all security-checking logic to collaborating objects. A lot more examples like this can be extracted from the framework.
DI Again, this is built into the Spring Framework itself, and of course as everything in the Spring architecture, this means that it is also inherited by the rest of the Spring projects, including Spring Security. Dependency injection (DI) is one of Spring’s most important features. Almost every component in Spring Security is configured through the use of dependency injection. The AccessDecisionManager is injected into the AbstractSecurityInterceptor, and AccessDecisionVoter implementations are injected into the AccessDecisionManager. And like this, most of the framework is built by composing components together through dependency injection.
Summary This was a complex chapter, but going through the inner workings of a software tool is definitely the best way to understand it and take advantage of it. And that is what you did. You got an in-depth explanation of Spring Security’s architecture, its major components, and the way it works from the inside. 115
Chapter 4
Spring Security Architecture and Design
You should now understand how the XML namespace works, how AOP fits into the framework, and how, in general, the Servlet Filter functionality is used to enforce web-level security. We demystified the “Spring magic” by going through all the components that help you add security to your applications in a seemingly simple way. You looked at some code snippets from the framework itself to get a greater appreciation of the work done in it, as well as to understand better why things work the way they do. You also studied the modularity inherent in the framework and saw how it helps to create software that is both flexible and extensible. Even with all we covered in this chapter, this was basically an introduction and a reference to have in hand when you read the upcoming chapters and you start looking at the options to secure your applications. From now on, you will understand where everything fits in the framework and how the different components link to each other. In the next chapter, you will start developing an example application. In the beginning, it will be a simple web application, and you will see how to secure it. You will use all your current knowledge of the framework to tweak the configuration and test different ways of implementing security at the web level.
116
CHAPTER 5
Web Security In this chapter, we will continue explaining how to apply security at the web layer for the Java Web-Based Applications you started in Chapter 3. You will see in detail the inner work of the security filter chain and the different metadata options at your disposal to define security constraints in your application. We will also explain in detail the taglib facility for enforcing security constraints at the view level, which was also introduced in Chapter 3.
Introducing the Simple Example Application In this chapter, you will be working on a simple test web application named favoritesmovies. The application itself will be very simple. (It won’t have any really useful functionality). However, we will try to cover most of the options available for securing web applications with Spring Security, even if some of the options don’t seem realistic for an application of this kind. First, you will set up the application. As in Chapter 3, you will create a Java Web Application and include Maven as the framework. Again, you will assume certain versions of the different tools you need. Mainly, you will use Java 11 and Maven 3.6.0 as you did in Chapter 3. Create the project as shown in Figure 5-1.
Figure 5-1. Creating a new dynamic web application Now you have an empty web project. The next step will be to convert it to Maven project. At the moment, you will use the same dependencies you had in your program from Chapter 3. So make sure your pom.xml file has the content from Listing 5-1.
Listing 5-1. First pom.xml for favoritesmovies 4.0.0warpss01_favoritesmoviescom.apress.pssfavoritesmovies1.0-SNAPSHOT 118
Chapter 5
Web Security
org.apache.maven.pluginsmaven-compiler-plugin3.211org.apache.maven.pluginsmaven-war-plugin2.4src/main/webapp warSourceDirectory> pss01_Securityfalsefavoritesmovies As shown previously, when creating a new Java Web Application project, a new index.jsp page is automatically generated under the WEB-INF folder. Just to test that it works, update the .jsp file to show simple “Hello World!” text, and then compile and execute the application running on the external Tomcat v9, as you did in Chapter 3. If you open your browser and go to http://localhost:8080/favoritesmovies, you will see the message “Hello World!” as the output of the index.jsp web page, as shown in Figure 5-2. 119
Chapter 5
Web Security
Figure 5-2. The first iteration of the application displays the simple Hello World! page Let’s start adding the security to this example. First, you need to configure pom.xml with the Spring Security 5 dependencies shown and explained in Chapter 3, such as •
spring-security-web
•
spring-security-config
•
spring-security-taglibs
Update your pom.xml file as shown in Listing 5-2.
Listing 5-2. pom.xml with All the Security and Framework Dependencies 4.0.0warpss01_favoritesmoviescom.apress.pssfavoritesmovies1.0-SNAPSHOT5.1.5.RELEASE5.1.5.RELEASE
org.apache.maven.pluginsmaven-war-plugin2.4src/main/webapp warSourceDirectory> favoritesmovies falsefavoritesmovies Create a new folder named view under the WEB-INF folder. It will hold all your .jsp web pages. Next, import all the Java classes you used in Chapter 3. Under the Java package com.apress.pss.springsecurity.controller, copy •
UserController
Under the Java package com.apress.pss.springsecurity.configuration, copy •
AppInitializer
•
SecurityConfiguration
•
SpringSecurityInitializer
•
UserConfiguration
Note that, for the purposes of web security, it doesn’t really matter if you use a Spring MVC controller, like you do here, or if you use simple servlets, as you did in Chapter 3, or for that matter, if you use any other Servlet-based framework for developing your application. Remember that, at the core, the web part of Spring Security basically attaches itself to the standard Java servlet filter architecture. So if your application uses servlets and filters, you can leverage Spring Security’s web support. 123
Chapter 5
Web Security
Since Spring Framework 4.3, there are some new HTTP mapping annotations based on @RequestMapping: •
@GetMapping
•
@PostMapping
•
@PutMapping
•
@DeleteMapping
•
@PatchMapping
For instance, @GetMapping is a specialized version of the @RequestMapping annotation, which will act as a shortcut for @RequestMapping(method = RequestMethod.GET). @GetMapping annotated methods to handle the HTTP GET requests matched with a certain given URI expression. Now that you’ve copied the Java classes from Chapter 3, you can start making changes to them. Let’s start with the Java controller file, where using @GetMapping you define the controls with values /, /index, and /movies, which will trigger the index.jsp and movies.jsp pages. The UserController Java class can be found in Listing 5-3.
@Controller public class UserController { @GetMapping ("/") public String home() { return "index"; } @GetMapping ("/index") public String index() { return "index"; } @GetMapping ("/movies") public String movies() { return "movies"; } } You can see that in this controller you specified the URL, the HTTP method, and the body of the request you will receive. The URL is built with a combination of the class-level RequestMapping annotation’s value concatenated with the method-level RequestMapping annotation’s value. So your URL is movies. The HTTP method is specified as POST in the RequestMapping annotation on the method. The RequestBody annotation in the method parameter simply tells Spring to populate the movie parameter with the string that comes in the body of the request. And the ResponseBody annotation at the method level tells Spring to use the return value of the method as the body content of the response. Next, let’s create a simple .jsp page named movies.jsp, as shown in Listing 5-4.
Listing 5-4. The movies.jsp Page
My favorites list of movies:
Il Postino La vita e' bella NottingHill 125
Chapter 5
Web Security
With no user authentication yet required, you can test your new functionality by building the application, opening the URL http://localhost:8080/favoritesmovies/ movies, and seeing the result in Figure 5-3.
Figure 5-3. Executing the project using a .jsp web page As you can see, the application using the control /movies will trigger the movies.jsp page and show the list of the movies. Let’s configure it now so that the /movies control will require an authenticated user with the ADMIN role. Update the SecurityConfiguration Java class from Chapter 3 to the code shown in Listing 5-5.
@Configuration @EnableWebSecurity public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired PasswordEncoder passwordEncoder; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .passwordEncoder(passwordEncoder) .withUser("admin").password(passwordEncoder. encode("admin123")).roles("ADMIN"); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/", "/index", "/login").permitAll() .antMatchers("/**").hasRole("ADMIN") .and().formLogin() .and().logout().logoutSuccessUrl("/index").permitAll() .and().csrf().disable(); } } The code is very simple, as basically the /, /index, and /login controls are permitted without authentication, while any other page via the /** will require an authenticated user with ADMIN as role. You will use the default login page generated automatically by Security Spring v5. Logout is needed when the logout link is used; this will be explained later. Finally, cross-site request forgery is disabled as discussed in Chapter 3. 127
Chapter 5
Web Security
Next, update the movies.jsp page to <%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> Spring Security authentication example
My favorites list of movies:
Il Postino La vita e' bella NottingHill
">Logout Now you have all the files needed and configured for this example. You want know to access the same URL as before to reach the movies.jsp page but this time Spring Security will ask you to provide admin credentials to access it. Restart the application and paste the following URL in your browser’s address bar: http://localhost:8080/ favoritesmovies/movies. This time, your browser should get redirected to the URL http://localhost:8080/ favoritesmovies/login and should show you the familiar login form from Chapter 3, which you can see in Figure 5-4. Log in as admin/admin123 and you can access the movies.jsp page as an admin.
128
Chapter 5
Web Security
Figure 5-4. Spring Security v5 login form Once the admin credential is correct, Spring Security will provide access to the movies.jsp page, as shown in Figure 5-5.
Figure 5-5. The movies page accessed with admin credentials Please notice that we added a logout link to actually log out the user if needed. We will explain more in detail how to use Spring Security login and logout URL later in this chapter. You will now look in more detail at what is going on in the application and how the configuration you just defined is helping to secure the application. You will follow the request throughout its trip through the framework and look at the different steps that it takes. When you make the HTTP request to the configured URL, and after your Servlet container deals with it, the request lands in the DelegatingFilterProxy, which in turn delegates the processing to the security FilterChainProxy. 129
Chapter 5
Web Security
In general, Spring Security utilizes a lot of filters. In the case of HTTP request filter, it will be used to •
Intercept the request.
•
Detect authentication (or absence of ).
•
Redirect to authentication entry point.
•
Pass the request to authorization service.
•
Send the request to the servlet or throw security exception.
In Spring Security v5, the most important filters are the following: Security Filter Chain: •
DelegatingFilterProxy
•
FilterChainProxy
Core Security Filters: •
FilterSecurityInterceptor
•
ExceptionTranslationFilter
•
SecurityContextPersistenceFilter
•
UsernamePasswordAuthenticationFilter
•
AnonymousAuthenticationFilter
Basic and Digest Authentication Filters: •
BasicAuthenticationFilter
•
DigestAuthenticationFilter
Session Management: •
SessionManagementFilter
CSRF Caveats: •
HiddenHttpMethodFilter
Preauthentication Scenarios: •
130
AbstractPreAuthenticatedProcessingFilter
Chapter 5
Web Security
LogoutSuccessHandler: •
LogoutFilter
Spring Security-Aware HttpServletRequestWrapper: •
SecurityContextHolderAwareRequestFilter
RequestCache: •
RequestCacheFilter
The HTTP request and authentication processes and filters are explained in Chapter 4. Let’s see what happen when incorrect or correct credentials are provided when logging in. When the browser is redirecting and asks for the URL /login, the following occurs: •
The process is the same as for the first request until it reaches the DefaultLoginPageGeneratingFilter. At this point, the filter detects that the request is for the URL /login and writes the login form’s HTML data directly in the response object. Then the response is rendered.
Now try to log in with incorrect credentials. Let’s follow the request through the framework to see what happens: 1. In the login form, type the username user and the password uspass. 2. When the form is submitted, the filters are activated again in the same order as before. This time, however, when the request arrives at the UsernamePasswordAuthenticationFilter, the filter checks whether the request is for the URL /login and sees that this is indeed the case. The filter extracts the username and password authentication information from the HTTP request parameters username and password, respectively. With this information, it creates the UsernamePasswordAuthenticationToken Authentication object, which then sends it to the AuthenticationManager (or more exactly, its default implementation, ProviderManager) for authentication. 131
Chapter 5
Web Security
3. The DaoAuthenticationProvider gets called from the ProviderManager with the Authentication object. The DaoAuthentication provider is an implementation of AuthenticationProvider, which uses a strategy of UserDetailsService to retrieve the users from whichever storage they live in. With the configuration you currently have, it will try to find a user with the username of user using the configured InMemoryUserDetailsManager (the implementation of UserDetailsService that maintains an in-memory user storage in a java.util.Map). Because there is no user with this username, the provider throws a UsernameNotFoundException exception. 4. The provider itself catches this exception and converts it into a BadCredentialsException to hide the fact that there is no such user in the application; instead, it treats the error as a common username-password combination error. 5. The exception is caught by the UsernamePasswordAuthenticationFilter. This filter delegates to an instance of an implementation of AuthenticationFailureHandler, which in turn decides to redirect the response to the URL /login?error. This way, the login form is shown again in the browser with an error message displayed. You can see this interaction in Figure 5-6.
Figure 5-6. Authentication filter when authentication details are incorrect Let’s now log in with the correct credentials. First, create a new endpoint in the controller to retrieve some simple text. In the controller UserController, create the method from Listing 5-6 called /showmovie.
132
Chapter 5
Web Security
Listing 5-6. A Simple Endpoint Method That Returns a Simple String @GetMapping("/showmovie") @ResponseBody public String showMovie() { return "movie x"; } Restart the application, go back to the URL http:/localhost:8080/ favoritesmovies/showmovie, and type admin as the username and admin123 as the password in the form. Then click the Login button. •
The request follows the same filter journey as before. This time, InMemoryUserDetailsManager finds a user with the requested username and returns that to DaoAuthenticationProvider, which creates a successful Authentication object.
•
After successful authentication, the UsernamePasswordAuthenticationFilter delegates to an instance of SavedRequestAwareAuthenticationSuccessHandler, which looks for the original requested URL (/showmovie) in the session and redirects the response to that URL.
When http://localhost:8080/favoritesmovies/showmovie is requested, the request works its way through the filter chain as in the previous cases. This time, though, you already have a fully authenticated entity in the system. The request arrives in the FilterSecurityInterceptor. •
The FilterSecurityInterceptor receives an access request to the URL /showmovie. Then it recovers the necessary credentials to access that URL (ROLE_ADMIN).
•
The AffirmativeBased access-decision manager gets called and in turn calls the RoleVoter voter. The voter evaluates the list of authorities of the authenticated entity and compares them with the required credentials to access the resource. Because the voter finds a match (ROLE_ADMIN is in both the Authentication authorities and the resource’s config attributes), it votes with an ACCESS_GRANTED vote.
133
Chapter 5
Web Security
•
The FilterSecurityInterceptor forwards the request to the next element in the request-handling chain, which in this case is Spring’s DispatcherServlet.
•
The request gets to the AdminController, which simply returns the String movie x, which then gets rendered to the browser. Figure 5-7 shows this result.
Figure 5-7. The showmovie page is returned when accessing with correct credentials •
This is the complete flow of the Authentication and Authorization process. Figure 5-8 shows this full interaction in a pseudo flow chart.
Figure 5-8. Overall flow of a successful authentication and authorization process 134
Chapter 5
Web Security
The Special URLs From the preceding explanation, you can see that Spring Security’s support for web security defines a few preconfigured URLs for you to use in your application. These URLs get special treatment in the framework. The main ones are the following: •
/login: This is the URL that Spring Security uses to show the login form for the application. The framework will redirect to this URL when an authentication is needed but doesn’t exist yet.
•
/logout: This URL is used by the framework to log out the currently logged-in user, invalidating the corresponding session and SecurityContext.
From the previous URLs, the first thing that comes to mind is how to configure your own login form in the application and, in general, how to customize the login process instead of using the default one. That is what we’ll do next.
Note As of Spring Security v5, /login replaced /j_spring_security_ check, which was in use until Spring Security 4.
Custom Login Form When you use Spring security v5, the user authentication request to your application is done via the http.authorizeRequests() method. When you configure the http element, via the http.authorizeRequests() method, as you did before, Spring Security takes care of setting up a default login and logout process for you, including a login URL, login form, default URL after login, and some other options. Basically, when Spring Security’s context starts to load up, it will find that there is no custom login page URL configured, so it will assume the default one and create a new instance of DefaultLoginPageGeneratingFilter that will be added to the filter chain. As you saw before, this filter is the one that generates the login form for you.
135
Chapter 5
Web Security
If you want to configure your own form, you need to do the following tasks. The first thing is to tell the framework to replace the default handling with your own. You define the following element as a child of the http.authorizeRequests() method in the SecurityConfiguration Java file: formLogin().loginPage("/name of the login page") This element tells Spring Security to change its default login-handling mechanism on startup. First, the DefaultLoginPageGeneratingFilter will no longer be instantiated. Let’s try this first configuration out. With the new configuration in place, restart the application and try to access the URL http://localhost:8080/favoritesmovies/ movies. You get redirected to the URL /login and get a 404 HTTP error because you haven’t defined any handler for this URL yet. This 404 page is shown in Figure 5-9.
Figure 5-9. Error 404 that appears when defining a new login handler page Let’s add a login controller in the UserController as shown in Listing 5-7.
Listing 5-7. Login Controller Added to the UserController @GetMapping("/login") public String loginPage() { return "login"; } Next add the following line to the SecurityConfiguration file: .and().formLogin().loginPage("/login").permitAll() Now create the login.jsp page from Listing 5-8 in the folder WEB-INF/views in your application. 136
Chapter 5
Web Security
Listing 5-8. custom login.jsp <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> Spring Security Custom Login Form
If you restart the application and again go to http://localhost:8080/ favoritesmovies/movies, you should see your new login form when you get redirected to the /login URL The form is shown in Figure 5-10. If you type admin as username and admin123 as password, you get access to the movie x page, as you did before with the default login form.
Figure 5-10. Custom login form If you take a look at the login.jsp, you can see certain names for the username field, password field, the remember me checkbox, and the action attribute of the form element. These are not random names. Spring Security expects the use of these particular names in order to treat the authentication process correctly. Also, the form should use POST for sending the information to the server because this is required by the framework. The “Remember Me” checkbox shown in the figure will be explained later.
138
Chapter 5
Web Security
The element supports many more configuration options, including changing the default username and password names for the authentication request parameters. The attributes might include •
always-use-default-target
•
authentication-details-source-ref
•
authentication-failure-handler-ref
•
authentication-failure-url
•
authentication-success-handler-ref
•
default-target-url
•
login-page
•
login-processing-url
•
password-parameter
•
username-parameter
•
authentication-success-forward-url
•
authentication-failure-forward-url
Restart the application, go to the URL /movies, and log in with admin/admin123. You should be able to access the application without any problem. Give this attribute the value /login. Then, in your login.jsp, add the content from Listing 5-9 just after the tag.
Listing 5-9. Snippet Showing an Error in the login.jsp <% if(request.getParameter("error") != null){ out.println("ERROR LOGIN"); } %> If you now restart the application and try to access the URL http:// localhost:8080/favoritesmovies/movies and use an incorrect username and password, you will get the login page again, but with the error message shown at the top. Look at Figure 5-11 for the page you should be getting. 139
Chapter 5
Web Security
Figure 5-11. A custom error shown in the custom form Note that this URL could be a different URL altogether, not related to the login URL at all. But the common pattern is to allow the user another attempt at logging in, showing her any errors. •
authentication-success-handler-ref: Reference to an AuthenticationSuccessHandler bean in the Spring application context. This bean is called upon successful authentication and should handle the next step after authentication, usually deciding the redirect destination in the application. A current implementation in the form of SavedRequestAwareAuthenticationSuccessHandler takes care of redirecting the logged-in user to the original requested URL after successful authentication.
•
authentication-failure-handler-ref: Reference to an AuthenticationFailureHandler bean in the Spring application context. It is used to handle failed authentication requests. When an authentication fails, this handler gets called. A standard behavior for this handler is to present the login screen again or return a 401 HTTP status error. This behavior is provided by the concrete class SimpleUrlAuthenticationFailureHandler.
Let’s develop a simple example implementation of the AuthenticationFailureHandler interface. It will simply return a 500 status code when failing to authenticate. Create the class CustomAuthenticationFailureHandler from Listing 5-10.
140
Chapter 5
Web Security
Listing 5-10. AuthenticationFailureHandler Implementation for ServerErrorFailureHandler package com.apress.pss.springsecurity.configuration; import org.springframework.security.web.authentication. AuthenticationFailureHandler; import org.springframework.security.core.AuthenticationException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException { response.sendError(500); } } Then, add to the SecurityConfiguration class file the following: .and().formLogin().loginPage("/login").permitAll().failureHandler(new CustomAuthenticationFailureHandler()) And the new bean: @Bean public AuthenticationFailureHandler customAuthenticationFailureHandler() { return new CustomAuthenticationFailureHandler(); } Restart the application, go to http://localhost:8080/favoritesmovies/movies, use a random username and password, and click the Submit button. You should get a 500 error in the browser. Listing 5-11 shows the fully updated SecurityConfiguration file so far.
@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/", "/login").permitAll() .antMatchers("/**").hasRole("ADMIN") .and().formLogin().loginPage("/login").permitAll(). failureHandler(new CustomAuthenticationFailureHandler()) .and().csrf().disable(); } @Bean public AuthenticationFailureHandler customAuthenticationFailureHandler() { return new CustomAuthenticationFailureHandler(); } }
Basic HTTP Authentication Sometimes, you can’t really use a login form for authenticating users. For instance, if your application is meant to be called by other systems instead of a human user, it doesn’t make sense to show a login form to the other application. This is a pretty common use case. Web services talk to each other without user interaction, ESB systems integrate systems with one another, and JMS clients produce and consume messages from other systems. In the context of HTTP-exposed interfaces that require no human user to access them, a common approach is to use HTTP basic authentication headers. HTTP authentication headers allow you to embed the security information (username and password) in the header of the request that you send to the server, instead of sending it in the body of the request, as is the case for the login form authentication.
143
Chapter 5
Web Security
HTTP uses a standard header for carrying this information. The header is appropriately named Authorization. When using this header, the client that is sending the request (for example, a browser) concatenates the username and the password with a colon between them and then Base64 encodes the resulting string, sending the result of this in the header. For example, if you use the username neve and the password nardone, the client creates the string neve:nardone and encodes it prior to sending it in the header. Let’s use basic HTTP authentication in your application. The first and only thing you need to do is remove the .formlogin() element in your configuration file SecurityConfiguration and add instead .httpBasic() After replacing it, restart the application and go to the URL http://localhost:8080/ favoritesmovies/movies in the browser. A standard HTTP authentication box will pop up asking you for your authentication details, as Figure 5-12 shows. Type admin/ admin123 as the username and password, and send the request. You will successfully arrive on the movies page that you already saw a couple of times before (Figure 5-5).
Figure 5-12. Standard HTTP authentication form, basic authentication configuration When you use the httpBasic configuration element, Spring Security's BasicAuthenticationFilter comes into action. A BasicAuthenticationEntryPoint strategy will be configured into the ExceptionTranslationFilter on startup. When you make the first request to the URL /movies, the framework behaves as before, throwing an access-denied exception that is handled by the ExceptionTranslationFilter. This filter delegates to a particular implementation strategy of AuthenticationEntryPoint— in this case, BasicAuthenticationEntryPoint. BasicAuthenticationEntryPoint adds 144
Chapter 5
Web Security
the header WWW-Authenticate: Basic realm="Spring Security Application" to the response and then sends an HTTP status of 401 (Unauthorized) to the client. The client should know how to handle this code and work accordingly. (In the case of a browser, it simply shows the authentication pop-up.) When you introduce the username and password, and submit the request, the request again follows the filter chain until it reaches the BasicAuthenticationFilter. This filter checks the request headers, looking for the Authorization header starting with Basic. The filter extracts the content of the header and uses Base64.decode to decode the string, and then it extracts the username and password. The filter creates a UsernamePasswordAuthenticationToken object and sends it to the authentication manager for authentication in the standard way. The authentication manager will ask the authentication provider to retrieve the user and then create an Authentication object with it. This process is standard and independent of using basic authentication or form authentication.
Digest Authentication Digest authentication is a very close sibling of basic HTTP authentication. Its main purpose is to avoid sending clear text passwords on the wire, as basic authentication does, by hashing the password prior to sending it to the server. This makes digest authentication more complex than basic authentication. Digest authentication works with HTTP headers the same way that basic authentication does. Digest authentication is based on the use of a nonce for hashing the passwords. A nonce is an arbitrary server-generated number that is used in the authentication process and is used only once. It is passed through the digest computation together with the username, password, nonce, URI being requested, and so on. In the authentication process, both the server and client do the digest computation and they should match. The main processing lies in two classes: DigestAuthenticationFilter and DigestAuthenticationEntryPoint. DigestAuthenticationFilter queries the request’s headers looking for the Authorization header, and then it checks that the header’s value starts with Digest. If this is the case, the request is carrying the security credentials that will be used for authentication. 145
Chapter 5
Web Security
DigestAuthenticationEntryPoint is the class that is invoked to generate a response that demands that a digest security authentication process begin. This class sets the header WWW-Authenticate with the correct values (including the nonce) so that the client agent (the browser) knows it has to start the digest authentication process. To configure the digest authentication, update the SecurityConfiguration file with the following lines: http.addFilter(digestAuthenticationFilter()) .exceptionHandling().authenticationEntryPoint(digestEntryPoint()) Next, add the following bean to add the Sigestauthentication filter: private DigestAuthenticationFilter digestAuthenticationFilter() throws Exception { DigestAuthenticationFilter digestAuthenticationFilter = new DigestAuthenticationFilter(); digestAuthenticationFilter.setUserDetailsService(userDetailsService Bean()); digestAuthenticationFilter.setAuthenticationEntryPoint(digestEntry Point()); return digestAuthenticationFilter; } Then, define the username and password using inMemoryUserDetailsManager: @Override @Bean public UserDetailsService userDetailsServiceBean() { InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager(); inMemoryUserDetailsManager.createUser(User.withUsername("admin"). password(passwordEncoder.encode("admin123")).roles( "ADMIN"). build()); return inMemoryUserDetailsManager; }
146
Chapter 5
Web Security
Finally, define the digestEntryPoint: @Bean DigestAuthenticationEntryPoint digestEntryPoint() { DigestAuthenticationEntryPoint bauth = new DigestAuthenticationEntryPoint(); bauth.setRealmName("Security Digest Authentication"); bauth.setKey("SecurityKey"); return bauth; If you restart the application and go to http://localhost:8080/favoritesmovies/ movies, you will be presented with a browser dialog box asking for a username and password exactly like the one that was shown for basic authentication. This is the DigestAuthenticationEntryPoint’s work. As explained, the entry point will fill the response object with the required headers so that the browser knows it needs to show the login form. Log in with username admin and password admin123, and you should be able to access the requested URL. The browser will create its own digested message with the password input included and put it in the header. It will also put the rest of the information—namely, nonce, cnonce, realm, and so on—in the Digest header. An example Digest header that is sent to the server with your current request is the following: 'Digest username="admin", realm=" Security Digest Authentication", nonce="MTM1NTY3NDc3NDIy....==", uri=" /movies", response="225ea6fbad618cfdf1da7d4f7efe53b8", qop=auth, nc=00000002, cnonce="376a9b27621880bd"' When the request reaches DigestAuthenticationFilter, the headers of the request contain the required digest authentication header. The information in this header arrives as a CSV string containing all the required information shown in the last paragraph, including the nonce and the client nonce (cnonce). (A nonce is an arbitrary number used only once in a cryptographic communication. See http://en.wikipedia.org/ wiki/Cryptographic_nonce.). The filter extracts the information from the header, retrieves the user from the UserDetailsService, and then computes the digest with the password from the retrieved user to see if the digest matches the one sent in the header by the client. If they match, access is granted.
147
Chapter 5
Web Security
Remember-Me Authentication The remember-me authentication functionality is used to allow returning users of the application to use it without needing to log in every time. Basically, the application remembers certain visitors, allowing them to just open the application and be greeted with their personalized version of the application, as if they were logged in. Remember-me functionality is very convenient for users; however, it is also very dangerous and recommended for private (from home) use only. The problem should be obvious. If you use an application from a public computer and this application remembers your profile information, the next person who accesses that application from that computer will be able to impersonate you with minimum effort. It is also common practice to offer just a limited amount of functionality in the remember-me session. This means that even if you are logged in automatically thanks to the remember-me functionality, you won’t have access to the whole functionality of the application. More sensitive parts of the application might require you to formally log in to use them. This is the case, for example, with Amazon.com. When you visit Amazon. com and log in, the next time you visit Amazon, the site will remember you, your recommendations, your name, and other information about you. But if you want to buy something, it will ask you to log in fully to access that functionality. Remember-me authentication is typically supported by sending a cookie to the browser, which then, on subsequent sessions in the application, is sent back to the server for auto login. How does remember-me functionality work in Spring Security? Remember-me functionality in Spring Security is supported mainly by two components: the RememberMeServices interface and the RememberMeAuthenticationFilter class. Let’s see how they work in the context of a request. When the application starts up, the RememberMeAuthenticationFilter is in the filter chain of the server. Also, a TokenBasedRememberMeServices is instantiated and injected into the AbstractAuthenticationProcessingFilter replacing the no-op NullRememberMeServices. Go to http://localhost:8080/favoritesmovies/movies, and log in with admin as the username and admin123 as password.
148
Chapter 5
Web Security
When the request gets into the application, UsernamePasswordAuthenticationFilter (a subclass of AbstractAuthenticationProcessingFilter) handles the authentication process in the standard way already explained. After the authentication is successful, UsernamePasswordAuthenticationFilter invokes the configured TokenBasedRememberMeServices's loginSuccess method. This method looks to see if the request contains the parameter remember-me in order to apply the remember-me functionality. (If the property alwaysRemember is set to true in the service, it will also apply the remember-me functionality.) Because you didn’t send this request, nothing will happen. So let’s add the parameter to the login form you have. Open the file login.jsp and somewhere inside the
This login.html page will do three things: 1. Send the email address and password to the LoginController Java class as an HTTP POST method. 2. Check if the email and password are typed correctly. 3. Provide the signup option to add a new user to the MongoDB database. To check if the credentials of the user are valid and where they should be redirected, create the Java Controller class named LoginController, as shown in Listing 8-10.
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.ModelAndView; import javax.validation.Valid; @Controller public class LoginController { @Autowired private CustomUserDetailsService userService; @RequestMapping(value = "/login", method = RequestMethod.GET) public ModelAndView login() { ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName("login"); return modelAndView; } @RequestMapping(value = "/signup", method = RequestMethod.GET) public ModelAndView signup() { ModelAndView modelAndView = new ModelAndView(); User user = new User(); modelAndView.addObject("user", user); modelAndView.setViewName("signup"); return modelAndView; } @RequestMapping(value = "/signup", method = RequestMethod.POST) public ModelAndView createNewUser(@Valid User user, BindingResult bindingResult) { ModelAndView modelAndView = new ModelAndView(); User userExists = userService.findUserByEmail(user.getEmail()); if (userExists != null) { modelAndView.addObject("message", "There is already a user registered with the username provided"); bindingResult .rejectValue("email", "error.user", "There is already a user registered with the username provided"); }
311
Chapter 8
Customizing and Extending Spring Security
if (bindingResult.hasErrors()) { modelAndView.setViewName("signup"); } else { userService.saveUser(user); modelAndView.addObject("successMessage", "User has been registered successfully"); modelAndView.addObject("user", new User()); modelAndView.setViewName("login"); } return modelAndView; } @RequestMapping(value = "/admin", method = RequestMethod.GET) public ModelAndView admin() { ModelAndView modelAndView = new ModelAndView(); Authentication auth = SecurityContextHolder.getContext(). getAuthentication(); User user = userService.findUserByEmail(auth.getName()); modelAndView.addObject("currentUser", auth.getName()); modelAndView.addObject("role", user.getRole()); modelAndView.addObject("adminMessage", "Content Available Only for Authenticated Admins!"); modelAndView.setViewName("admin"); return modelAndView; } @RequestMapping(value = "/user", method = RequestMethod.GET) public ModelAndView user() { ModelAndView modelAndView = new ModelAndView(); Authentication auth = SecurityContextHolder.getContext(). getAuthentication(); User user = userService.findUserByEmail(auth.getName()); modelAndView.addObject("currentUser", auth.getName()); modelAndView.addObject("role", user.getRole()); modelAndView.addObject("userMessage", "Content Available Only for Authenticated Users!"); 312
Chapter 8
Customizing and Extending Spring Security
modelAndView.setViewName("user"); return modelAndView; } @RequestMapping(value = {"/","/welcome"}, method = RequestMethod.GET) public ModelAndView home() { ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName("welcome"); return modelAndView; } @RequestMapping(value = {"/forbidden"}, method = RequestMethod.GET) public ModelAndView forbidden() { ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName("forbidden"); return modelAndView; } } This Java class works as page controller that, based on the URL requested, will redirect to a page like welcome, forbidden, etc. When requesting the /admin or /user URL, the Java class will verify if the user credentials are ok to redirect to the preferred page. The modelAndView.addObject is used to collect information and messages to be displayed on the HTML pages. All the page errors will be managed via the Java Controller class MyErrorController shown in Listing 8-11.
@Controller public class MyErrorController implements ErrorController { public MyErrorController() {} @GetMapping(value = "/error") public ModelAndView renderErrorPage(HttpServletRequest httpRequest) { ModelAndView errorPage = new ModelAndView("errorPage"); String errorMsg = ""; int httpErrorCode = getErrorCode(httpRequest); switch (httpErrorCode) { case 400: { errorMsg = "Http Error Code: 400. Bad Request"; break; } case 401: { errorMsg = "Http Error Code: 401. Unauthorized"; break; } case 404: { errorMsg = "Http Error Code: 404. Resource not found"; break; } case 500: { errorMsg = "Http Error Code: 500. Internal Server Error"; break; } } errorPage.addObject("errorMsg", errorMsg); return errorPage; } private int getErrorCode(HttpServletRequest httpRequest) { return (Integer) httpRequest .getAttribute("javax.servlet.error.status_code"); } 314
Chapter 8
Customizing and Extending Spring Security
@Override public String getErrorPath() { return "/error"; } } This Java class in case of a whitelabel error will redirect to the errorPage.html page, which will display the error message received from the Java class. Notice that this will work when adding the following line in the application.properties: server.error.whitelabel.enabled=false The errorPage.html page looks like Listing 8-12.
Listing 8-12. errorPage.html Web Page Security with Spring Boot
Error on page!
Home The errorpage.html page is shown in Figure 8-13.
Figure 8-13. errorPage.html page 315
Chapter 8
Customizing and Extending Spring Security
If a forbidden page is requested, the LoginController Java class will redirect to the forbidden.html page, which is shown in Listing 8-13. This is configured in the SpringSecurityConfiguration Java class as follow: exceptionHandling().accessDeniedPage("/forbidden");
Listing 8-13. forbidden.html Web Page Security with Spring Boot
Error on page!
Errortype: Home The forbidden.html page is shown in Figure 8-14.
Figure 8-14. The forbidden.html page The next step is to create your needed models, named User and Role, which will store the MongoDB collections. First, create the User model shown in Listing 8-14.
316
Chapter 8
Customizing and Extending Spring Security
Listing 8-14. The User Java Class package com.apress.SpringSecurityMongoDB.domain; import import import import import
import java.util.Set; @Document(collection = "users") public class User { @Id private String id; @Indexed(unique = true, direction = IndexDirection.DESCENDING) private String email; private String password; private String role; @DBRef private Set roles; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } 317
Chapter 8
Customizing and Extending Spring Security
public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getRole() { return role; } public void setRole(String role) { this.role = role; } public Set getRoles() { return roles; } public void setRoles(Set roles) { this.roles = roles; } @Override public String toString() { return "User{" + "id=" + id + ", email='" + email.replaceFirst("@.*", "@***") + ", password='" + password.substring(0, 10) + ", role=" + role + '}'; } }
318
Chapter 8
Customizing and Extending Spring Security
Basically this User Java class will manage getting and adding user data from and to the MongodDB. For instance, for an email address, the following code will get the email and set the email value to the MongoDB database: public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } You do the same to create the Role model, shown in Listing 8-15.
Listing 8-15. The Role Java Class package com.apress.SpringSecurityMongoDB.domain; import import import import
@Document(collection = "roles") public class Role { @Id private String id; @Indexed(unique = true, direction = IndexDirection.DESCENDING) private String role; public String getId() { return id; } public void setId(String id) { this.id = id; }
319
Chapter 8
Customizing and Extending Spring Security
public String getRole() { return role; } public void setRole(String role) { this.role = role; } } Again, like for the User Java class, get and set will be used to get data and set data from and to the MongoDB database. The next step is to create two Java repository interfaces, one for User and one for Role, to find email and role values from the database. They are shown in Listings 8-16 and 8-17.
Listing 8-16. The User Java Interface package com.apress.SpringSecurityMongoDB.repository; import com.apress.SpringSecurityMongoDB.domain.User; import org.springframework.data.mongodb.repository.MongoRepository; public interface UserRepository extends MongoRepository { User findByEmail(String email); }
Listing 8-17. The Role Java Interface package com.apress.SpringSecurityMongoDB.repository; import com.apress.SpringSecurityMongoDB.domain.Role; import org.springframework.data.mongodb.repository.MongoRepository; public interface RoleRepository extends MongoRepository { Role findByRole(String role); }
320
Chapter 8
Customizing and Extending Spring Security
The next step is to create the SpringSecurityMongoDbApplication, which is used to set the new registered user the credential value of ADMIN or USER, as shown in Listing 8-18.
@SpringBootApplication public class SpringSecurityMongoDbApplication { public static void main(String[] args) { SpringApplication.run(SpringSecurityMongoDbApplication.class, args); } @Bean CommandLineRunner init(RoleRepository roleRepository) { return args -> { Role adminRole = roleRepository.findByRole("ADMIN"); if (adminRole == null) { Role newAdminRole = new Role(); newAdminRole.setRole("ADMIN"); roleRepository.save(newAdminRole); } Role userRole = roleRepository.findByRole("USER"); if (userRole == null) { Role newUserRole = new Role(); newUserRole.setRole("USER"); roleRepository.save(newUserRole); } 321
Chapter 8
Customizing and Extending Spring Security
}; } } The SpringSecurityMongoDbApplication class receives the user credential value from the login.html page and creates and sets it for the user via the following lines: For Role: Role newUserRole = new Role(); newUserRole.setRole("USER"); roleRepository.save(newUserRole); For User: Role newAdminRole = new Role(); newAdminRole.setRole("ADMIN"); roleRepository.save(newAdminRole); The next Java class is CustomUserDetailsService, which is a Java service class needed for user functions to implement the Spring Security User Details Service. It is shown in Listing 8-19.
import java.util.*; @Service public class CustomUserDetailsService implements UserDetailsService { @Autowired private UserRepository userRepository; @Autowired private RoleRepository roleRepository; @Autowired private BCryptPasswordEncoder bCryptPasswordEncoder; public User findUserByEmail(String email) { return userRepository.findByEmail(email); } public void saveUser(User user) { user.setPassword(bCryptPasswordEncoder.encode(user.getPassword())); String getroletype; getroletype = user.getRole(); Role userRole = roleRepository.findByRole(getroletype); user.setRoles(new HashSet<>(Arrays.asList(userRole))); userRepository.save(user); } @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { User user = userRepository.findByEmail(email); if(user != null) { List authorities = getUserAuthority(user. getRoles()); return buildUserForAuthentication(user, authorities); } else { throw new UsernameNotFoundException("username not found"); } } 323
Chapter 8
Customizing and Extending Spring Security
private List getUserAuthority(Set userRoles) { Set roles = new HashSet<>(); userRoles.forEach((role) -> { roles.add(new SimpleGrantedAuthority(role.getRole())); }); List grantedAuthorities = new ArrayList<>(roles); return grantedAuthorities; } private UserDetails buildUserForAuthentication(User user, List authorities) { return new org.springframework.security.core.userdetails.User(user. getEmail(), user.getPassword(), authorities); } } This Java class is used when you need to •
Load user information like an email address or the user GrantedAuthority list via findUserByEmail() and loadUserByUsername()
•
Save a new user via saveUser()
•
Get a user authority via getUserAuthority()
•
Build a user for authentication via buildUserForAuthentication()
Note that you return a new org.springframework.security.core.userdetails. User() to store the user authentication credential including email, password, and authorities. Now finally you can create your login.html page; see Listing 8-20.
Listing 8-20. The login.html Page Spinrg Boot Security 5 and MongoDB 324
Chapter 8
Customizing and Extending Spring Security
Please login as user or admin:
Invalid email or password.
The login.html page collects the info needed to login such as email and password and sends it via the POST method to the /login URL, which via the LoginController Java class will be validated using the following lines: @RequestMapping(value = "/login", method = RequestMethod.GET) public ModelAndView login() { ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName("login"); return modelAndView; The login.hmtl page is shown in Figure 8-15.
325
Chapter 8
Customizing and Extending Spring Security
Figure 8-15. The login.html page If the email or password values are invalid, the error message “Invalid email and password” will be displayed as shown in Figure 8-16.
Figure 8-16. Invalid email and password If the user is authenticated as User, she will be redirected to the user.html page, which is shown in Listing 8-21.
Listing 8-21. The user.html Page Spring Security 5 and MongoDB.
Welcome [[${#httpServletRequest.remoteUser}]]!
You are succesfully logged as an authenticated User!
326
Chapter 8
Customizing and Extending Spring Security
This content is only shown to authenticated users.
This content is only shown to users with authority "USER".
Note that you used sec:authorize="isAuthenticated()" and sec:authorize="has AnyAuthority('USER')" via Thymeleaf to determine if the user is actually authenticated and if she has USER as her authority. The user.html page is shown in Figure 8-17.
Figure 8-17. The user.html page Click the Sign Out button to log out. If the user is authenticated as ADMIN, she will be redirected to the admin.html page, which is shown in Listing 8-22.
327
Chapter 8
Customizing and Extending Spring Security
Listing 8-22. The admin.html Page Spring Security 5 and MongoDB.
Welcome [[${#httpServletRequest.remoteUser}]]!
You are succesfully logged as an Admin!
N1: This content is only shown to authenticated users.
As you did for the user.html page, here in the admin.html page you use sec:auth orize="isAuthenticated()" and sec:authorize="hasAnyAuthority('ADMIN')" via Thymeleaf to determine if the user is actually authenticated and if she has ADMIN as her authority. The admin.html page is shown in Figure 8-18.
328
Chapter 8
Customizing and Extending Spring Security
Figure 8-18. The admin.html page Click the Sign Out button to log out. Now if you wish to create a new user in the MongoDB, you select click the “Signup as a new user” button on the login.html page and send via the HTTP GET method to the /signup page which via the LoginController Java class will be validated using the following lines: @RequestMapping(value = "/signup", method = RequestMethod.GET) public ModelAndView signup() { ModelAndView modelAndView = new ModelAndView(); User user = new User(); modelAndView.addObject("user", user); modelAndView.setViewName("signup"); return modelAndView; } If you want to add a new user in the MongoDB database, you invoke the singup.html page shown in Listing 8-23.
Listing 8-23. The signup.html Page
329
Chapter 8
Customizing and Extending Spring Security
Spring Boot Security 5 and MongoDB Example
Add a new user to the MongoDB.
Signup.hmtl will trigger the LoginController for the /signup HTTP POST method as shown here: @RequestMapping(value = "/signup", method = RequestMethod.POST) public ModelAndView createNewUser(@Valid User user, BindingResult bindingResult) { ModelAndView modelAndView = new ModelAndView(); User userExists = userService.findUserByEmail(user.getEmail()); if (userExists != null) { modelAndView.addObject("message", "Username provided is already registered in the db! "); 330
Chapter 8
Customizing and Extending Spring Security
bindingResult .rejectValue("email", "error.user", "Username provided is already registered in the db!"); } if (bindingResult.hasErrors()) { modelAndView.setViewName("signup"); } else { userService.saveUser(user); modelAndView.addObject("successMessage", "New user has been registered successfully"); modelAndView.addObject("user", new User()); modelAndView.setViewName("login"); } return modelAndView; } In this case, the LoginController class using the /signup HTTP POST method will •
Check if the user email address is already registered in the MongoDB database, display the message “Username provided is already registered in the db!”, and redirect again to /signup.
•
Or trigger the creation of a new user and display the message “New user has been registered successfully”.
The signup.html page is shown in Figure 8-19.
Figure 8-19. The signup.html page
331
Chapter 8
Customizing and Extending Spring Security
Your final and complete project will look like Figure 8-20.
Figure 8-20. Complete project overview You completed your example but we’ll describe better password encryption and error handling next.
P assword Encryption One thing you might have noticed in this example and all the previous examples is that you are storing and retrieving passwords in plain text. Spring Security offers an abstraction to encrypt passwords in the form of the interface that was in Spring Security v4 as org.springframework.security.authentication.encoding.PasswordEncoder but changed in Spring Security v5 to org.springframework.security.crypto. password.PasswordEncoder in the core framework. Since Spring Security v5 it was declared the PasswordEncoder in org. springframework.security.authentication.encoding was deprecated so this interface was removed since it was not designed for a randomly generated salt. Also, Spring Security v5 changed how encoded passwords are handled since, before v5, every application could only work with one password encoding algorithm. 332
Chapter 8
Customizing and Extending Spring Security
Before Spring Security v5, the default PasswordEncoder was NoOpPasswordEncoder which required plain text passwords. PasswordEncoder is now replaced with BCryptPasswordEncoder. Then of course we could face right away problems like, how do we deal with old applications using old password encodings that cannot easily migrate? And what happens when the password storage changes again? Since Spring Security v5, a concept of password encoding delegation named DelegatingPasswordEncoder was introduced, which generally allow us to use different encodings for different passwords. DelegatingPasswordEncoder works so that any password is encoded using the current password storage recommendations, allows for validating passwords in modern and legacy formats, and allows for upgrading the encoding in the future. Spring Security v5 automatically recognizes the algorithm using an identifier prefixing the encoded password. The general format for a password is {id} encodedPassword. Here are some examples of encoded password: {noop}password {bcrypt}$1c$23$TeLobXCxerA5EDFqwKJFn.KUT2VCdsaAwD67WETa9A.7Hrt123Kea {sha256}123ghjss67239nsgewt6772ghsjd22789sa2jkalsaldsdj72349378sadsajdsljd 32874732salkdf {pbkdf2}hdsfjhdsfurehyuhhfsjdhfhfkjhdsfkjhdfkjshdfösakfucxvhkjvhcoiweshfjd sfewtsrewrwyir {scrypt}?shdf4Md3234hlkf843Lhhdsjfsd7982374293hsdkjfshdfh91273fgsjjdhfgssj fdggfsdllflsdlfsdfsdUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUOc/9bdYSrN1oD? As you saw for the bcrypt example, the {bcrypt} prefix was used to tell Spring Security to use the bcrypt password encoder. In case of a password hash with no prefix, the delegation process uses a default encoder, which is by default the StandardPasswordEncoder. So the password will have PasswordEncoder id = bcrypt and encodedPassword = $1c$23$TeLobXCxerA5EDFqwKJFn.KUT2VCdsaAwD67WETa9A.7Hrt123Kea so when matching it would delegate to BCryptPasswordEncoder. Note that you can easily construct an instance of DelegatingPasswordEncoder using PasswordEncoderFactories like this: PasswordEncoder passwordEncoder = PasswordEncoderFactories. createDelegatingPasswordEncoder(); 333
Chapter 8
Customizing and Extending Spring Security
So, how does the password matching work? It works based upon the {id} and the mapping of the id to the PasswordEncoder provided in the constructor, as you saw before. In general, by default, the result of invoking matches(CharSequence, String) with a password and an id that is not mapped (including a null id) will trigger the IllegalArgumentException. You can customize this behavior by using Delegating PasswordEncoder.setDefaultPasswordEncoderForMatches(PasswordEncoder). Finally, the Java configuration for BCrypt could be like this: import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @Bean public BCryptPasswordEncoder passwordEncoder() { BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); return bCryptPasswordEncoder; }
Handling Errors and Entry Points Spring Security has a very nice error-handling mechanism built in. It offers a comprehensive set of exceptions that map to the most common cases of security errors you could expect to have in a system. Continuing with Spring Security’s great single- responsibility architecture, the handling of error conditions is mostly encapsulated in one single class. This class is (and you have studied it before) the org.springframework. security.web.access.ExceptionTranslationFilter, and it basically deals with two types of exceptions: AccessDeniedException and AuthenticationException. If any other exception is caught by this filter, it will simply rethrow it as a RuntimeException. Spring Security offers a couple of extension points you can use to plug in functionality in the form of a custom entry point and a custom access-denied handler. Basically, what happens is that when an AuthenticationException is caught by the filter, an AuthenticationEntryPoint’s “commence” method is called with the HTTP request, the HTTP response, and the exception. The particular AuthenticationEntryPoint implementation that is configured in the application can decide what to do with the exception and, more importantly, what to do with the HTTP response. By default, the implementation used is org.springframework.security.web. authentication.LoginUrlAuthenticationEntryPoint, which redirects to the login URL, which, as you already know, is /login in the root of your application. 334
Chapter 8
Customizing and Extending Spring Security
The entry point is invoked when an AuthenticationException is thrown or when an AccessDeniedException is thrown and the current Authentication object is anonymous. As another artificial example, you will implement an entry point that will add a cookie each time an authentication attempt is made from the client side and then show the basic authentication scheme in the browser. This means you will set a cookie in the response that will increment its value every time the entry point is invoked. Then the cookie is sent back to the server every time with the new value. This means that the counter for the attempts is not stored in the server at all; it is stored just in the client cookie and is sent back and forth between the client and the server. So the server receives the cookie, increments its value, and then sends it back. It is not really innovative in any way, but it allows us, again, to show that you can override the entry point to do different things. Most likely, the entry point is overridden when a different kind of authentication scheme is being used. For example, one of the standard entry points applies when using authentication schemes that require showing a login page, while others (in particular the BasicAuthenticationEntryPoint) are used to set up particular values in the response to inform the browser how to treat it. In most cases, the implementation of an entry point goes hand in hand with the implementation of a new security filter. If you take a look at the implementations provided by the framework, you can see this clear relationship in BasicAuthenticationEntryPoint – BasicAuthenticationFilter, LoginUrlAuthenticationEntryPoint – UsernamePasswordAuthenticationFilter (kind of; this one is not as one-to-one), CasAuthenticationEntryPoint – CasAuthenticationFilter, and some others. Basically, the entry points set the groundwork and then the filter processes the subsequent request. Figure 8-21 shows the relationship between the ExceptionTranslationFilter and the AuthenticationEntryPoint.
335
Chapter 8
Customizing and Extending Spring Security
Figure 8-21. The ExceptionTranslationFilter and AuthenticationEntryPoint relationship Listing 8-24 shows an example of entry-point implementation, and Listing 8-25 shows the configuration needed to make it work. It is a very simple implementation, but it is actually a little more complex than the standard BasicAuthenticationEntryPoint.
Listing 8-24. AuthenticationEntryPoint Implementation That Creates an Attempts Cookie and a Basic Authentication Response package com.apress.pss.security; import java.io.IOException; import import import import
import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; public class AttemptsAuthenticationEntryPoint implements AuthenticationEntryPoint{ public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) 336
Chapter 8
Customizing and Extending Spring Security
throws IOException, ServletException { response.addHeader("WWW-Authenticate", "Basic realm=\"theapp\""); response.addHeader("Set-Cookie", "authentication_attempts="+(getDeniesCookie(request)+1)+"; Max- Age=3600; Version=1"); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage()); } private int getDeniesCookie(HttpServletRequest request) { for(Cookie cookie:request.getCookies()){ if(cookie.getName().equals("authentication_attempts")){ return Integer.parseInt(cookie.getValue()); } } return 0; } } The code in Listing 8-25 has a lot in common with the code from org.springframework. security.web.authentication.www.BasicAuthenticationEntryPoint because it also tells the browser in the response to start a new basic authentication input process. The main difference is, of course, in the retrieval and processing of the authentication_ attempts cookie. The cookie is first retrieved from the request in the private method getDeniesCookie. Then the value of this cookie (which is assumed to be an integer) is increased by one and reset in the response header in the following line: response.addHeader("Set-Cookie", "authentication_attempts="+(getDeniesCookie(request)+1)+"; Max-Age=3600; Version=1");
337
Chapter 8
Customizing and Extending Spring Security
Listing 8-25. Spring Configuration Needed to Use a Custom Entry Point ...... ... If a different kind of exception than the ones mentioned before is thrown (particularly, an AccessDeniedException for a fully authenticated user), an AccessDeniedHandler is invoked instead of the AuthenticationEntryPoint. The default AccessDeniedHandler implementation that is invoked is AccessDeniedHandlerImpl, which also by default will set a 403 error status in the response and let the browser render its default 403 page. However, you can also configure an errorPage property in the handler to determine that a forward (an internal dispatching mechanism inside the application, different from a redirect) is made to a customized error page, which is probably the most common personalization you will do when using the AccessDeniedHandler. You can also define your own implementation of AccessDeniedHandler, and that is what you will do here to illustrate the point, but you will also use a custom errorPage property much as you would use it in the AccessDeniedHandlerImpl. The implementation, as in the previous example, will simply add an extra header in the response in the form of a cookie that specifies the number of “access-denied” responses received from the particular computer and browser from where the requests are coming. Listing 8-26 shows an example of the implementation of the handler.
private int getDeniesCookie(HttpServletRequest request) { for(Cookie cookie:request.getCookies()){ if(cookie.getName().equals(ACCESS_DENIES)){ return Integer.parseInt(cookie.getValue()); } } return 0; } } As mentioned, the code is mostly the same as the AccessDeniedHandlerImpl. You just added the cookie in the response, which will be set to incremental values every time this handler is invoked. You can see that there is also the logic for processing the errorPage property in case it is set. A servlet dispatcher forward will be done to this error page URL, which means that it will have access to the same request that is used inside this class.
Changing the Security Interceptor The security interceptor is a class you rarely find yourself modifying or replacing because the default implementations cover the most common scenarios of filter security and method-level security. However, you can extend it for use in different kinds of applications that don’t strictly fit into the web app-business method services scheme. For example, Spring Integration has its own security interceptor implementation in the form of the org.springframework.integration.security. channel.ChannelSecurityInterceptor. Instead of working with simple method invocations or filter invocations, it works with a different abstraction, which is the org. springframework.integration.security.channel.ChannelInvocation. This means that it basically intercepts “send” and “receive” calls on a determined secured channel. Spring Integration also uses the class org.springframework.integration.security. channel.ChannelSecurityMetadataSource as the org.springframework.security. access.SecurityMetadataSource implementation for message channels. The creation of a different security interceptor, as the Spring Integration example shows, is basically for when you want to add Spring Security’s authorization support to applications that don’t follow the standard web-service way of doing things. However, 340
Chapter 8
Customizing and Extending Spring Security
keep in mind that a previous authentication mechanism must be in place, as the security interceptor will look for the different components that it needs to grant access to a resource. This means that an Authentication object must exist in the SecurityContext, the AccessDecisionManager must be configured, and so on. Listing 8-27 shows the security interceptor implementation from Spring Integration.
Listing 8-27. ChannelSecurityInterceptor Java Class /* * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.integration.security.channel; import java.lang.reflect.Method; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.security.access.SecurityMetadataSource; import org.springframework.security.access.intercept. AbstractSecurityInterceptor; import org.springframework.security.access.intercept. InterceptorStatusToken; import org.springframework.util.Assert;
341
Chapter 8
Customizing and Extending Spring Security
/** * An AOP interceptor that enforces authorization for MessageChannel send and/or receive calls. * * @author Mark Fisher * @author Oleg Zhurakousky * @see SecuredChannel */ public final class ChannelSecurityInterceptor extends AbstractSecurityInterceptor implements MethodInterceptor { private final ChannelSecurityMetadataSource securityMetadataSource; public ChannelSecurityInterceptor() { this(new ChannelSecurityMetadataSource()); } public ChannelSecurityInterceptor(ChannelSecurityMetadataSource securityMetadataSource) { Assert.notNull(securityMetadataSource, "securityMetadataSource must not be null"); this.securityMetadataSource = securityMetadataSource; } @Override public Class> getSecureObjectClass() { return ChannelInvocation.class; } public Object invoke(MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); if (method.getName().equals("send") || method.getName(). equals("receive")) { return this.invokeWithAuthorizationCheck(invocation); } return invocation.proceed(); } 342
Chapter 8
Customizing and Extending Spring Security
private Object invokeWithAuthorizationCheck(MethodInvocation methodInvocation) throws Throwable { Object returnValue = null; InterceptorStatusToken token = super.beforeInvocation(new Channel Invocation(methodInvocation)); try { returnValue = methodInvocation.proceed(); } finally { returnValue = super.afterInvocation(token, returnValue); } return returnValue; } @Override public SecurityMetadataSource obtainSecurityMetadataSource() { return this.securityMetadataSource; } } In this code, you can see that ChannelInvocation wraps a MethodInvocation before calling the beforeInvocation method on the parent class. Apart from wrapping, it will also do some inner processing to make the current executing channel available to be queried for ConfigAttributes. Also note that the interceptor will do its security validations only if the method being invoked is one of the standard “send” and “receive” methods of Spring Integration. The ChannelSecurityMetadataSource that is being used in the code is the one that will be queried for obtaining the security metadata attributes. It is the one that knows how to extract this information from Spring Integration Channels and the related org. springframework.integration.security.channel.ChannelAccessPolicy. It is very unlikely you will override the security interceptor in your own applications. However, it is good to know that you could do it and also understand why you would want to do it. As the example for Spring Integration shows, one reason you would want to replace a security interceptor (or add an additional one) is because you have certain abstractions, to which you want to apply interception-based security, that don’t fit either URL 343
Chapter 8
Customizing and Extending Spring Security
interception or simple method interceptions. You want to give a more meaningful name to your interception logic and also filter certain things that are not filtered by default with the default implementations. This is what the Spring Integration example is doing. It is intercepting MessageChannel communication, which is the domain element that makes sense in its context. However, in the end, it is basically intercepting methods and filtering to intercept only the methods send and receive, which again are the ones that make sense in the particular context.
Spring Security Extensions Project There is a whole project dedicated to the development of Spring Security extensions, where people from the community can develop their own extensions on top of Spring Security. In this way, they can decouple these extensions from the main Spring Security project, allowing it to evolve independently. The home of this extensions project is https://spring.io/projects/spring-security. If you visit this page, you will see that currently there are two extensions projects in existence: Kerberos integration and SAML2 integration. There is also the OAuth integration, which lives on its own as an individual project. You can find it at https:// docs.spring.io/spring-security/oauth/. Each project in Spring Security Extensions can implement as many parts of the framework as it needs in order to work correctly. This means that many of the extension points that we defined in this chapter (and some others) could be overridden in a particular extension in order to do its work.
S ummary In this chapter, we showed how the modularity in the architecture of Spring Security pays off when you want to customize or extend its behavior. We showed how to create a Spring Boot Web Application using Spring Security v5 and MongoDB. We also showed some of the different and most common extension points that Spring Security offers so that you can adapt its functionality to your particular application while keeping the core functionality, making the work easier for you by leveraging this functionality. After reading this chapter, and with all the theory and practice from previous chapters, you should feel confident enough to implement functionality that goes beyond the out-of- the-box offerings of the framework. 344
CHAPTER 9
Integrating Spring Security with Other Frameworks and Languages This chapter will explore Spring Security in the context of other application frameworks and languages that run on the JVM. You saw in previous chapters that the two main ways of using Spring Security are in the web layer in the shape of filters and in the business layer with Spring AOP. This means that you could use Spring Security in any application that is built on top of the Servlet technology or in any application that is willing to use Spring and Spring AOP to handle its object life cycles and interactions. In the following sections, you’ll see examples of both cases. We’ll start by looking at a couple of popular Java frameworks (one of which is also Spring-based) and how to use Spring Security with them. These frameworks are the popular Struts 2 web framework and Spring Web Flow, another member of the SpringSource suite. After studying these two frameworks, we’ll take a brief look at a few Java Virtual Machine (JVM) programming languages (and some of their related frameworks) and how to use Spring Security with them. We’ll be looking at Groovy in the context of its web-development framework Grails, JRuby in the context of Rails, and Scala embedded in a Spring Web Application. We will not go into any of these frameworks or languages in much detail, because that would be beyond the scope of this book. The purpose of this chapter is simply to explain how to use Spring Security in a wider context.
Integrating Spring Security with Other Frameworks and Languages
Spring Security with Struts 2 Struts 2 is a popular Java web framework built by merging the original Struts project and the WebWork project. Struts 2 was always intended to be a complete evolution from the original Struts framework, adapted to a new generation of powerful web frameworks, and it really kept little of the original Struts principles and implementations. Struts 2 is widely used and is as good a candidate as any other external servlet-based framework to integrate with Spring and Spring Security. Struts 2 is an MVC (model view controller) framework built on top of the standard Java servlets technology, so many of the web-based security principles you have read about in this book apply without modification. If you want to use only the URL-level, web-based security you studied in Chapter 5, you just need to configure your web.xml and your Spring Security filter chain accordingly or you can implement using the WebApplicationInitializer. More information about the Struts 2 releases can be found at https://struts. apache.org/releases.html. In this book, Struts version 2.5.20 will be used. As with the rest of frameworks and languages in this chapter, we won’t explain Struts 2 in any depth (because there are many good books available on that subject). We’ll explain just enough so that you can use Spring Security with it. In fact, we assume that if you are reading this section it, is probably because you are already using Struts 2 and want to integrate Spring Security with it. However, to explain a little more, Figure 9-1 shows the big picture of how Struts 2 works.
346
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
Figure 9-1. How Struts 2 works When a request comes to the application, a front controller (http://en.wikipedia. org/wiki/Front_Controller) implemented in a servlet filter takes care of the request. It sends the request through a set of configured interceptors that perform different kinds of functionality before and after the action is invoked. The action is then invoked, which carries with it all the business logic required by the current request. After the action finishes, a result object representing the view is created and the interceptor stack is invoked in inverse order as before (while returning the result) until a response is finally returned to the client. The first step for you to do is of course download the libraries to include in your Maven project from this link: https://struts.apache.org/download.cgi#struts-ga. Struts 2 can be download in a full distribution, including everything, or just as a separate set of libraries, sources, documentation, etc.
347
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
You’ll be using Maven to build this project. So let’s create a new Maven project as shown in Figure 9-2. Then follow the prompts by selecting a new starter project at the first prompt and selecting some relevant groupId and artifactId names. (We chose com.apress.pss.struts and struts-example, respectively, as shown in Figure 9-2. You can use the same to follow along in the examples easily).
Figure 9-2. The Struts project Now create a simple, functional Struts 2 web application with the components you need to create the test, including Spring integration (although it uses an old DTD version of the configuration XML file). The file structure after executing that command is shown in Figure 9-3.
348
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
Figure 9-3. Struts 2 application file structure after executing the Maven command In Figure 9-3, you can see that there is a file named struts.xml; this is the main Struts 2 configuration file, and you can see its contents in Listing 9-1.
349
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
Listing 9-1. The struts.xml File welcome.jspadmin.jspwelcome.jsp In Listing 9-1, the action, "helloWorld", doesn’t define a class name on the attribute class. In this case, it defines a simple string that identifies a Spring bean in the spring- security.xmlspring-security.xml that you’ll see later. When a request comes in, the framework looks at the definitions from this file to determine how to handle the particular request. (This definition is simplistic, but it’s good enough to make the point.) If it sees the name of a class in the action attribute, the framework instantiates a new object of that class to handle the request. If the framework detects that it is not a class, it looks for a bean with that ID in the Spring configuration and gets the object from there. All this is possible in the framework thanks to the struts2-spring-plugin plugin, which enhances Struts 2 with Spring functionality and makes it possible to define actions (and other components such as interceptors) in Spring beans. Please notice the usage of struts-2.5.dtd as Structs 2.5 version.
350
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
What you’ll do now is rewrite the default spring-security.xml, replace it with the one from Listing 9-2, and add the familiar dependencies from Listing 9-3 to the pom.xml file.
Listing 9-2. The spring-security.xml File for the Struts 2 Application
351
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
Listing 9-3. Spring Security Dependencies in the pom.xml File 5.1.5.RELEASE5.1.5.RELEASEorg.springframeworkspring-core${springframework.version}org.springframeworkspring-web${springframework.version}org.springframeworkspring-webmvc${springframework.version} 352
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
org.springframework.securityspring-security-web${springsecurity.version}org.springframework.securityspring-security-config${springsecurity.version} Next, you need to add the Spring Security filter to the web.xml. This filter should execute before the Struts 2 filter, so an authentication object is populated before reaching the action. Remember that Struts 2 works with filters and not with servlets. Listing 9-4 shows the web.xml file you can use as a test. Note that we removed some filter definitions we don’t care to use in the example.
Listing 9-4. The web.xml with the Spring Security Filter and Struts 2 Filter struts-exampleindex.jspcontextConfigLocationclasspath:spring-security.xml 353
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
Integrating Spring Security with Other Frameworks and Languages
springSecurityFilterChain/*jspSupportServletorg.apache.struts2.views.JspSupportServlet 5 Next, we simplified the .jsp files inside the jsp folder like this: the file helloWorld. jsp just contains the string “This is your first "Secured" Struts2 Application!” inside, nothing else. Struts 2 has a built-in system to handle the exceptions that might be thrown in your application. This mechanism would get in the way of Spring Security’s exception- handling system, which depends on AccessDeniedException, among other exceptions, to be thrown to alter the flow of execution. For example, when showing a login form, you need to deactivate the Struts 2 exception-handling mechanism. To do that in the file struts.xml, you add the line just below the other line that contains a defined element. Finally, you need to secure your action. Make your HelloWorldAction look like Listing 9-5.
Listing 9-5. HelloWorldAction Secured package com.apress.pss.struts; import javax.servlet.http.HttpServletRequest; import org.apache.struts2.ServletActionContext; public class HelloWorldAction { private String username;
355
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
public String execute() { HttpServletRequest request = ServletActionContext.getRequest(); this.setUsername(request.getUserPrincipal().getName()); return "SUCCESS"; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } } Now if you restart your application and visit http://localhost:8080/struts- example/helloWorld, you’ll be presented with the standard Spring Security login form. If you use the login username admin and password admin123, you’ll be able to access the page. Figure 9-4 shows the login, the HelloWorld. and the logout page.
356
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
Figure 9-4. The secured Struts 2 application and the accessed action
Spring Security with Spring Web Flow Spring Web Flow is a framework, built on top of Spring MVC, that allows you to link different steps of a web-driven process into a fluent workflow. In other words, it allows you to define in a declarative way the different steps that a web application can go through while you are interacting with it. Basically, you use it to define a set of rules and 357
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
transitions between the user interface (UI) parts of a web application and the back-end process that each transition should trigger. Please check this link for all information about Spring Web Flow: https://projects.spring.io/spring-webflow/. When you create a new Maven project, you need to add the Spring MF dependency like this: org.springframework.webflowspring-webflow2.5.1.RELEASE Graphically, Spring Web Flow works, in a simplified form, as shown in Figure 9-5. The example is a fake web page for a simplified product. The boxes represent various states (the View state, Action state, Decision state, Subflow state, and others), and the arrows represent transitions.
Figure 9-5. The Simple Spring Web Flow scheme shows that, from a product page, you can go to the review page or buy the product To implement this simple flow with Spring Web Flow, you’ll create a new project. As is the case for most of the examples, you’ll use Maven to build and manage your project. So let’s do it. Let’s create a new Maven project, as shown in Figure 9-6.
358
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
Figure 9-6. New Spring Web Flow Maven project Now you’ll create all the configuration files. Replace the pom.xml file in the new project with the one shown in Listing 9-6, and replace the web.xml file with the one shown in Listing 9-7. Then, in the WEB-INF directory, create the files shown in Listings 9-8 and 9-9 with the names spring-servlet.xml and spring-security.xml, respectively.
Listing 9-6. The pom.xml File with Spring Security and Spring Web Flow Dependencies 4.0.0com.apress.psswebflow-example0.0.1-SNAPSHOTwar5.1.5.RELEASE5.1.5.RELEASE 359
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
Integrating Spring Security with Other Frameworks and Languages
11maven-war-plugin3.2.3WebContentwebflow-example
Listing 9-7. The web.xml for Spring Web Flow and Spring Security Spring MVC Applicationspringorg.springframework.web.servlet.DispatcherServlet 1
362
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
spring/org.springframework.web.context.ContextLoaderListener contextConfigLocation /WEB-INF/spring-security.xml springSecurityFilterChainorg.springframework.web.filter.DelegatingFilterProxy springSecurityFilterChain/*welcome.jsp You should already be familiar with the content of Listing 9-7. It is a web.xml file that includes Spring's ContextLoaderListener, which loads the Spring application-context file given in context-param "contextConfigLocation". It also defines Spring’s DispatcherServlet servlet, which takes care of setting up Spring
363
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
MVC by loading the appropriate configuration file. You also define the already familiar springSecurityFilterChain filter. Both the security filter and the dispatcher servlet are configured to handle every URL in the system.
Note In Spring MVC, the name of the DispatcherServlet servlet is important because that name will match the name of the Spring configuration file that will be used in the application to configure the application. For example, in this case, by defining the DispatcherServlet with the name “spring”, Spring will expect to find a file with the name spring-servlet.xml in the WEB-INF folder where the beans for the web layer should be defined. Listing 9-8. The spring-servlet.xml File That Imports the Flow /WEB-INF/.jsp 364
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
This file is very simple, and its only job is to import another file (the example- webflow.xml file), which will contain the entire Spring Web Flow configuration. This configuration will remain in a different file just to keep it separated from the main servlet file.
Listing 9-9. The spring-security.xml File
365
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
This is another file you should be able to understand easily by now. Listing 9-9 shows a very basic Spring Security configuration. You are defining just a single user with role ROLE_ USER, which will be enough for your tests. You are not defining any URL security rules here because that is not what you want to do in this Spring Web Flow example. You want to add security at the flow level (its states), and that is what we will show you how to do. Now you need to define the web-flow configuration of the application as well as the actual web flows themselves. Again, this will be a simplistic example just to show how the functionality works. The example will be based on Figure 9-6. Listing 9-10 shows the web-flow configuration file, and Listing 9-11 shows your only flow definition.
Listing 9-10. The example-webflow.xml in the WEB-INF Folder 366
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
This file defines the general configuration for Spring Web Flow. The main part of the file is the element flow-registry and its attribute flow-builder-services. This element is where the location of the flows in the application are defined. Currently, you’ll define only one flow, which will be in the product.xml file. Also, note the way your views will get resolved when referenced in a view state in a flow. The class o rg. springframework.webflow.mvc.builder.MvcViewFactoryCreator is the one that will resolve view locations. By default, it will resolve view files by looking in the flow definition directory for files whose names are the names of the view states concatenated with .jsp at the end. This simply means that if a view is named review, it will look for a file named review.jsp.
367
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
Listing 9-11. The product.xml Simple Flow in the /WEB-INF/flows/ Folder This is the actual flow definition. You are simply defining three view states here. As we said before, each of these view states will map to a physical view file in the application. The view files, by default behavior, should be located in the same directory as this flow file and should be named according to the view states concatenated with .jsp at the end. So you should have in the WEB-INF/flows/products directory files named activation. jsp, success.jsp, and failure.jsp. They are shown in Listings 9-12, 9-13, and 9-14, respectively.
Listing 9-12. The activation.jsp for the Main View State
Hello Spring WebFlow!
Activate Spring WebFlow:
368
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
Listing 9-13. The success.jsp for the Review State
Spring WebFlow Activation Successful!
Listing 9-14. The failure.jsp for the Buy View State of the Flow
Spring WebFlow Activation Failed!
In the previous three .jsp files, you defined the three view states in your web flow. Listings 9-12 and 9-13 have transition triggers in the form of the eventId parameter in the href links. ${flowExecutionUrl} makes reference to the current executing state of the flow. So, in the case of the main state the first time you access the page, the value of ${ flowExecutionUrl } is /product?execution=e1s1, making the full URL to the next flow states something like /product?execution=e1s1}&_eventId=review. If you visit http://localhost:8080/product, you’ll be taken to the main view state. That view displays activate.jsp, where you will have one button to activate and one to cancel the activation, as Figure 9-7 shows. The moment you click the activate button you activate the Spring Web Flow process and the success.jsp opens. Let’s use Spring Security to do just that. Spring Security and Spring Web Flow integrate nicely because both are part of the Spring portfolio. Securing flows with Spring Security is easy. First, you need to add to the current element from the file example-webflow.xml (the element in Listing 9-15) and add the content shown in Listing 9-16 somewhere in the same file. 369
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
Listing 9-15. The flow-executor with Spring Security Listener
Listing 9-16. The Spring Security Listener Bean
Figure 9-7. The activation.jsp, which is the entry point into the web flow By implementing the preceding content, you integrate Spring Security into Spring Web Flow. All you need to do now is decide which parts of the flow to secure and what constraints to define to secure them. But before that, we’ll explain briefly what this SecurityFlowExecutionListener is doing for Spring Web Flow. Spring Web Flow offers an abstraction in the form of the interface org. springframework.webflow.execution .FlowExecutionListener, which allows you to implement classes to listen to or observe the life cycle of a flow execution. When a listener implementation is registered for the flows, it can intercept the flow execution at different points in its life cycle. In that sense, it is similar to the AOP concepts you studied before. org.springframework.webflow.security.SecurityFlowExecutionListener is a listener implementation that intercepts three particular points in the life cycle of the flow: sessionCreating, stateEntering, and transitionExecuting. In each of these interceptions, the listener delegates to a configured org.springframework.security. access.AccessDecisionManager like the ones we explained in different parts of this 370
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
book. If an AccessDecisionManager implementation is not provided when the bean is defined, a new role-based access decision manager will be created on the fly. So now you want to secure the view-state "activation" and allow access to it only to authenticated users with role ROLE_USER. To do that, you simply define the element as a child of the "activation" element. After you do that, if you restart the application and try to access the "buy" state, you’ll find yourself presented with the familiar Spring Security login screen. If you log in with the username admin and the password admin123, you’ll be able to reach the "buy" state. The resulting "buy" state view is shown in Figure 9-8.
Figure 9-8. Spring Web Flow Activation page You could also secure the whole flow if you want by having the element be a direct child of the element. This means that, to access any state of the flow, a user needs to have the required permissions. Go ahead and try it yourself; it should be very straightforward.
Spring Security in Other JVM Languages We are not experts in any of the languages that follow, and maybe we won’t use them in the examples in the most idiomatic way. (We know Ruby best because we often use it both at work and during leisure time. It’s one of our favorite languages.) However, our objective is just to show that, with some tweaking, you can integrate Spring Security into projects that are written in a language other than Java. You’ll see that the support for Spring Security in other JVM languages is sometimes straightforward and comprehensive (like when using Groovy and its Grails framework); at other times, you might need to roll your own integration solution to support it. In the upcoming sections, we give a brief overview of how to integrate Spring Security into what we consider to be the three major JVM languages other than Java: Groovy, JRuby, and Scala. Remember that ultimately Spring Security is no more than a set of Java libraries built on top of Spring Framework, which allows you to plug authentication/authorization security mechanisms into your applications. So it should be possible to integrate it into 371
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
any Java (Java JVM) application you have. Of course, not all functionality will apply to all applications. For example, the filter chain won’t make sense in a non-web application.
Spring Security and Ruby (JRuby) Ruby is definitely one of our favorite languages, and we spend a lot of time working with it. It combines great syntax with great language constructs, and it’s a pleasure to work with. Ruby is an object-oriented dynamic language with a focus on productivity, concision, and simplicity. The standard Ruby interpreter is written in C and was known as MRI Ruby until version 1.9. From version 1.9 forward, the official interpreter is known as the YARV interpreter. Ruby is an incredibly popular language, and most of its popularity stems from the widespread use of its incredible web framework, Ruby on Rails (RoR), also known simply as Rails. Rails is an MVC framework that places great emphasis on convention over configuration practices. It’s a very productive framework you can use to develop simple web applications in a fast and easy way if you follow the conventions enforced by the framework. We won’t give an in-depth explanation of either Ruby or Rails because that would be beyond the scope of this book. We also assume that if you’re reading this section, you probably know about them and just want to learn how to integrate Spring Security into them, or to learn if it’s at all possible. However, we’ll try to give small explanations of Ruby concepts when we use them in the examples. JRuby is a fully functional implementation of the Ruby programming language written in Java. You can use it to run Ruby programs inside a Java virtual machine and interact with your other JVM languages—mainly, of course, Java. What we’ll show in this section is a simple tutorial for integrating Spring Security into a Rails application and for deciding if it’s even worth doing. In the JRuby case, there is no plugin like the one you find in Grails. In fact, the integration between Java and Ruby is not as smooth as the integration between Java and Groovy. Basically, you’ll have to roll your own implementation to make the integration work. First things first. Let’s install JRuby. The JRuby version used for this book is version 9.2.6.0. You’ll use the simple installation here: just downloading a file, uncompressing it, and adding its executables to the path. The following is the procedure we used on a Windows machine (which should be similar to other operating systems): 372
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
1. Go to the directory where you want to install JRuby. 2. Download and install the file from https://s3.amazonaws.com/ jruby.org/downloads/9.2.6.0/jruby_windows_x64_9_2_6_0. exe into the following directory: c:\jruby-9.2.6.0. 3. You should have access to JRuby now. Execute jruby –v, and you should get the version of JRuby you just installed, as shown in Figure 9-9.
Figure 9-9. JRuby version page 4. Next, install Rails: jruby –S gem install rails, as shown in Figure 9-10.
Figure 9-10. Installing Rails
373
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
5. Now create your Rails application. As in the rest of the book, this will be an application with very little functionality just to show you how to use Spring Security. Let’s call this application simply demo. From a directory of your choice, write the following command: rails new demo. You now have a new basic Rails application in the system. You can run it by going to the directory demo that you just created from the command line and entering rails s. That command will start a WEBrick server and run the application on that server. If you are following the example, you should be able to visit http://localhost:3000 on your computer and access the default Rails application home page. In the process of creating the Rails application, a lot of infrastructure code and a well-defined directory structure was generated for you. If you take a look at the demo directory (which is the root of your application), you’ll see what we mean. Anyway, as we said before, we won’t go into any depth examining the Rails framework or Ruby itself. We’ll simply show with a rudimentary example how you could approach integrating Spring Security into a Ruby on Rails application. We are assuming you already know Ruby and Rails.
Web-Layer Security in Rails When you ran your Rails application in the previous section, you ran it with a Ruby server (WEBrick). This server doesn’t know anything about Java servlets, so you might guess that it’s not possible to run Spring Security web-layer security with this server— and you would be correct. What you need to do is run your Rails application in a standard Java web container, and that is what you’ll do next. First, install warbler, a gem you use to create standard WAR files from your Rails application. To install warbler, use the command jruby –S gem install warbler. After warbler is installed, you can execute jruby –S warble in the root directory of your application and it will create demo.war. That’s good, but let’s not deploy it just yet. Let’s add some functionality to it first, and then let’s add Spring Security–level security. You’ll add two simple routes to your application. One will return the string SECURED and will be accessible only to logged-in users. The other one will return the string UNSECURED and will be available to any user. 374
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
Let’s create a pair of controllers: one for admin users and one for standard users. You’ll call these controllers simply AdminsController and StandardsController. Type the following two commands in the root of your demo application to generate them: rails g controller admins rails g controller standards The execution of those commands generates output describing the artifacts that got generated. You’ll then edit those controllers to look like Listings 9-17 and 9-18. You can find the controllers in the standard Rails location in the app/controllers directory.
Listing 9-17. AdminsController with a Secured Action class AdminsController < ApplicationController def secured render :text => "This is top secret code" end end
Listing 9-18. StandardsController with an Unsecured Action class StandardsController < ApplicationController def unsecured render :text => "Anybody can read this meaningless message" end end The next thing you need to do is to copy all the Java libraries you need to use into the lib directory of your Rails application: aopalliance-1.0.jar commons-codec-1.3.jar commons-logging-1.1.1.jar javax.servlet-api-4.0.1.jar spring-aop-5.1.8.RELEASE.jar spring-asm-5.1.8.RELEASE.jar spring-beans-5.1.8.RELEASE.jar spring-context-5.1.8.RELEASE.jar spring-core-5.1.8.RELEASE.jar 375
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
spring-expression-5.1.8.RELEASE.jar spring-jdbc-5.1.8.RELEASE.jar spring-security-config-5.1.5.RELEASE.jar spring-security-core-5.1.5.RELEASE.jar spring-security-crypto-5.1.5.RELEASE.jar spring-security-web-5.1.5.RELEASE.jar spring-tx-5.1.8.RELEASE.jar spring-web-5.1.8.RELEASE.jar The next step is to enable your Rails application to be run in a standard Java web server, such as Tomcat. For that, you’ll use the gem warbler. Warbler is a JRuby exclusive gem you use to convert different kinds of Ruby applications into standard Java packaging artifacts, such as JAR and WAR files. In your case, you’ll obviously be creating a WAR file. Warbler uses internally the gem jruby-rack and packs it into the WAR-based application. This gem is the core of the integration between Ruby Rack–based applications (such as Rails applications) and Java web servlet–based applications, which is what you need. Jruby-rack works as a translation layer. It first initializes the Ruby part of the application with a servlet listener (org.jruby.rack.rails.RailsServletContextListener), and then, on each request, it intercepts the calls to the server with a servlet filter, org.jruby.rack. RackFilter. When this filter gets the HttpServletRequest, it will translate these Java-based requests into Rack requests that will pass through to the Rails application, as these are the requests Rails will understand as it is built on the Rack model. Of course, there is a lot more detail regarding warbler and jruby-rack, but for the moment, the explanations presented here should be enough for you to continue with the example. In Figure 9-11, you can see a graphical illustration of how jruby-rack works.
376
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
Figure 9-11. A jruby-rack mapping of the Java and Ruby worlds To use warbler with Spring Security, you need to change the web.xml file that gets automatically generated by it. To do this, you need to copy the web.xml.erb that comes with warbler (which resides in the gem installation directory of Ruby and which is ~/. rvm/gems/jruby-1.8/gems/warbler-1.3.6/web.xml.erb on my computer) into the config directory of your demo application. Then you need to edit it and make it look like Listing 9-19. By doing this, you are including the Spring Security configuration in your web.xml file as you did in previous chapters. Next, copy the applicationContext-security.xml file from Listing 9-20 into the root of your demo application. Here you have your familiar Spring Security configuration from Chapter 2, with a few modifications. You are simply securing the URL /admin/* for members of the Scarvarez family. Then you need to edit warbler’s configuration file. To do that, you execute the command jruby –S warble config in the root of your application. That execution generates a warble.rb file inside the config directory of the application. Make sure that that file looks like Listing 9-21. In the file, you are ensuring JRuby will be compatible with Ruby 1.9 and that the Spring configuration file will be included in the WEB-INF directory in the generated WAR file when warbler builds this file. That’s all you need, so let’s create the WAR file again. From the root directory of your application, execute JRUBY_OPTS=--1.9 warble. This command doesn’t work in Windows. You should execute just warble and set the JRUBY_OPTS variable separately. This generates the WAR file with your Rails application embedded on it. Deploy it to your web application server. (We deployed it to Tomcat 9.) 377
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
After it is deployed, you can visit the corresponding URLs. On our computer, the behavior is as follows: When we visit http://localhost:8080/demo/standard/message, we see the message “Anybody can read this meaningless message.” However, if we visit http://localhost:8080/demo/admin/message, the familiar login page is shown. After we log in with the username massimo and the password nardone, we see the following message on the page: “This is top secret code.” This is the behavior we expected. We have secured our Rails application with basic web-layer security.
Listing 9-19. The web.xml.erb in the config Folder, Including Configuration for Spring Security <% webxml.context_params.each do |k,v| %> <%= k %><%= v %> <% end %> springorg.springframework.web.servlet.DispatcherServlet servlet-class> 1spring/org.springframework.web.context.ContextLoaderListener listener-class> 378
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
contextConfigLocation /WEB-INF/applicationContext-security.xml springSecurityFilterChainorg.springframework.web.filter.DelegatingFilterProxy filter-class> springSecurityFilterChain/*RackFilterorg.jruby.rack.RackFilterspringSecurityFilterChain/*RackFilter/* <% if webxml.jndi then [webxml.jndi].flatten.each do |jndi| %> <%= jndi %>javax.sql.DataSource 379
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
Container <% end; end %>
Listing 9-20. The applicationContext-security.xml Security for /admin/∗ URLs 380
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
Listing 9-21. The warble.rb Configuration File Warbler::Config.new do |config| config.dirs = %w(app config lib log vendor tmp) config.includes = FileList["db"] config.webinf_files += FileList["applicationContext-security.xml"] config.webxml.jruby.compat.version = "1.9" end
Spring Security, Groovy, and Grails Groovy is one of the strongest contenders for the number one spot in the non-Java JVM language space. It has a great community and currently is supported and managed by the SpringSource people. Groovy is a programming language that tries to combine the power of Java with the elegance and developer-friendly characteristics of some dynamic programming languages—taking ideas mainly from well-known and much-loved languages such as Python and Ruby. Groovy successfully creates an environment where different kinds of programmers, with some practice, can feel at home (experienced Java developers and Ruby developers, for example). Groovy has many advantages when you compare it with other JVM languages— the main ones being, in our opinion, the easy transition from Java to Groovy and the interoperability between the two languages. With regard to the transition part, it is easy to take a Java program and make it a Groovy program. As a matter of fact, you don’t have to do anything. A Java program is already a valid Groovy program. This is good from one point of view but bad from another. Even when you can compile a Java program as a Groovy one, it doesn’t make sense. Ultimately, you are using Groovy to take advantage of the great features it has compared to Java. Those great features include a lot of metaprogramming techniques, new powerful constructs like closures, faster development cycles, and a clearer and more concise and developer-friendly syntax. Grails is a web framework written in Groovy and intended to be used with Groovy. It is built on top of the Spring Framework and is heavily influenced by Ruby on Rails. Grails is an attempt to make a friendlier and lighter framework in a JVM-based language that can be picked up quickly. Also, it offers better and more concise ways to develop 381
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
applications. At the same time, it is built upon some of the strongest Java libraries and frameworks, such as Spring and Hibernate. Let’s install Groovy library for the example and specify the Groovy SDK. Download the standard Groovy SDK tool from the official distributions available at http://groovy- lang.org/download.html. Unpack it into any directory and specify this directory as the library home. In our case, it is C:\apache-groovy-sdk-2.5.6. Now you can create your new example.
sing Grails to Secure the Web Layer with U URL Rules Grails and Spring Security integration is incredibly simple because Spring Security is the default security solution for Grails applications and a comprehensive plugin exists to support it. Here, you’ll create a simple Grails application, and you’ll learn how to secure it with Spring Security. As usual, we’ll introduce this application in a step-by-step process. Download and install the latest version of Grails if you still don’t have it. You can download it from its homepage at http://grails.org/. We are running this example with version 3.3.9 of Grails. To install it, simply unzip the downloaded the grails-3.3.9.zip file (in our case, it is C:\grails-3.3.9) and then set the environment variable GRAILS_HOME to point to the new expanded directory. Also, add to your PATH environment variable the path GRAILS_ HOME/bin. At the command line, go to any directory where you want to create the application and execute grails create-app demo-grails to create the application. Next, generate a couple of controllers: a secured controller and an unsecured controller. Execute grails create-controller secured inside the generated application directory. And then execute grails create-controller unsecured. Now you need to create an action in each controller and make them look like Listings 9-22 and 9-23. Put these files in the directory grails-app/controllers/demo/ grails of your application. Run the application now with grails run-app and visit either http:// localhost:8080/demo-grails/secured/message or http://localhost:8080/demo- grails/unsecured/message, and you should be able to access both URLs without a problem. The next logical step is to secure the secured URL. 382
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
As we said before, Grails is built on top of Spring, so it’s only logical that an integration with Spring Security should be straightforward. Grails is built in a clever modular way so that you can add functionality in the form of plugins. Let’s install the Spring Security plugin by executing grails install-plugin spring-security-core (which, in our current version, installs the version 1.2.7.3). Then execute the command grails s2-quickstart demo.security User Role to generate the needed models to support users and roles in the application. Next, you’ll create a couple of test users to try out security. Open the file BootStrap. groovy (in the grails-app/conf directory), and make it look like the code in Listing 9-24. Here you are creating two users: one with role ROLE_ADMIN and the other one with the role ROLE_USER. You can see that this code uses the classes generated in the previous step, User and Role. As with previous examples, you’ll secure certain URLs so that they are accessible only for users with a specific role. To do this in Grails, you need to add the code from Listing 9-25 to the end of the file Config.groovy (which resides in the directory grails-app/conf). The code is self-explanatory. It simply tells Grails to use an intercept URL map for security. This map defines URL paths with wildcards (in Ant-style syntax, which we covered before for standard Java rules) and the roles that are allowed to access such URLs. Run the application by executing the command grails run-app in the root directory. Now, if you try to access http://localhost:8080/demo-grails/unsecured/message or http://localhost:8080/demo-grails/secured/message, you’ll get redirected to a login page. If you log in with the username luna and the password password, you’ll be granted access to the secured URL and not the unsecured one. However, if you log in with the username neve and the password password, you’ll be granted access to http:// localhost:8080/demo-grails/unsecured/message but you’ll get an “access denied” error when trying to access http://localhost:8080/demo-grails/secured/message. You can see that you have properly secured access to the URLs. The first thing that is hard to see from this example is that you are actually using Spring Security. We say this because you haven’t defined any filters, any authentication managers, any voters, any user service, or any authentication provider. Actually, you haven’t defined any Spring bean at all. You could easily assume that something else is used under the covers because there is nothing specific to Spring Security here. This is a good thing, and one of the nice features of Grails. The use of plugins gives you sensible common defaults and leaves you only with the responsibility of defining the things that are exclusive to your business problem. In the case of security, you need to define your users, their passwords, and the roles and access permissions for your application. 383
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
Listing 9-22. SecuredController with a Secured Message in the Grails Application package demo.grails class SecuredController { def message() { render "Incredibly confidential message" } }
Listing 9-23. UnsecuredController with an Unsecured Message in the Grails Application package demo.grails class UnsecuredController { def message() { render "message for everyone" } }
Listing 9-24. BootStrap.groovy Setting Up a Couple of Test Users with Security Roles Assigned to Them import demo.security.Role import demo.security.User import demo.security.UserRole class BootStrap { def init = { servletContext -> def adminRole = new Role(authority: 'ROLE_ADMIN').save(flush: true) def userRole = new Role(authority: 'ROLE_USER').save(flush: true) def testAdmin = new User(username: 'luna', enabled: true, password: 'password') testAdmin.save(flush: true) 384
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
Listing 9-25. An Excerpt from Config.groovy, Where We Add the URLs That Need to Be Secured grails.plugins.springsecurity.securityConfigType = "InterceptUrlMap" grails.plugins.springsecurity.interceptUrlMap = [ '/secured/**': ['ROLE_ADMIN'], '/unsecured/**': ['ROLE_USER'], ] Grails’ Spring Security plugin gives you more access to the functionality offered by Spring Security and, of course, it also allows you to customize it by overriding the defaults. One of the things it supports is the use of SpEL for access rules. To test this in the context of this example, and with the simplest security SpEL expression we can think of, simply replace the security section introduced in the file Config.groovy (from Listing 9-25) with the content from Listing 9-26. In this listing, as you can see, you are using the security expression hasRole, which you studied in previous chapters. Note that this is a simple example with hasRole, but here you have access to the full suite of expressions offered by Spring Security SpEL support. For example, you could use authentication.name == 'luna' as an expression instead of hasRole.
385
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
Listing 9-26. An Excerpt from Config.groovy, Which Uses SpEL Instead of Simple Roles grails.plugins.springsecurity.securityConfigType = "InterceptUrlMap" grails.plugins.springsecurity.interceptUrlMap = [ '/secured/**': ["hasRole('ROLE_ADMIN')"], '/unsecured/**': ["hasRole('ROLE_USER')"], ]
Using Grails Security at the Method Level Grails’ Spring Security plugin supports method-level security the same way as it does with Java. This means you can put @grails.plugins.springsecurity secured annotations (or the standard Spring Security @Secured annotation as well) in your controller classes and Spring Security will make sure they are secured according to those annotations. Doing this is very simple. Let’s keep working on the application from the last section. First, replace the current content of the file Config.groovy (in the grails-app/conf directory) from the line that reads grails.plugins.springsecurity. securityConfigType = "InterceptUrlMap" onward with the simple line grails. plugins.springsecurity.securityConfigType = "Annotation". Then replace the content of class SecuredController (in the grails-app/controllers/demo/grails directory) with the content of Listing 9-27 and the content of class UnsecuredController (in the grails-app/controllers/demo/grails directory) with the content of Listing 9-28. After you do this, if you restart the application, you should get the same access constraints that you got in the previous section when you secured the URLs.
Listing 9-27. SecuredController with the @Secured Annotation package demo.grails import grails.plugins.springsecurity.Secured; class SecuredController { @Secured(["ROLE_ADMIN"]) def message() { render "Incredibly confidential message" } } 386
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
Listing 9-28. UnsecuredController with the @Secured Annotation package demo.grails import grails.plugins.springsecurity.Secured; class UnsecuredController { @Secured(["ROLE_USER"]) def message() { render "message for everyone" } } In these last two sections, we just scratched the surface of the functionality available using Grails’ Spring Security plugin. You can do virtually everything you can do using the standard Java support. For a comprehensive guide to using the Grails plugin, take a look at its official page at http://plugins.grails.org/plugin/grails/springsecurity-core.
Spring Security and Scala Scala is probably the strongest language (in the sense of number of adopters and the liveliness of the community around it) running on top of the Java Virtual Machine apart from Java itself. (Scala also has a version that runs on the .NET platform.) Scala is a very powerful, general-purpose programming language that tries to merge the best of the object-oriented and functional programming paradigms. Scala is a language that aims to provide a concise and elegant alternative to the world of enterprise Java programming, while keeping the type safety of Java. As we said, it comes with functional programming features built in, adding a whole new layer of power for the seasoned object-oriented programmer that increases productivity. We are not experts in Scala or functional programming (far from it as we have just recently started to look at them) but we will give you the core definitions and the main characteristics of both and show you how to use them with Spring and Spring Security. Functional programming is a paradigm in which programs are composed of functions that receive inputs and produce outputs, while also avoiding the use of state and mutability. This is in clear contrast to object-oriented programming, where the main abstraction is the object and its internal state, and mutability is a very common thing. 387
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
By not allowing mutability and state, functional programming presents itself as a good alternative for programming concurrent programs, because programmers don’t need to worry about synchronization between concurrent processes or threads. In Scala, functions can be passed around (as function parameters or function return values) as simple values. In this sense, they behave pretty much like any other simple value (for example, a string), and this is an important concept in a functional programming language. In Scala, functions are first-class objects. According to the literature, Scala’s name comes from the words scalable language. This implies that Scala supports object-oriented and functional programming paradigms and that it is suitable for simple scripting tasks or full enterprise applications. Combining this capability with an elegant and concise syntax makes the language scalable in terms of the number of domains and uses that it can address. Let’s install Scala now. There are two ways of installing Scala: as via the IntelliJ IDEA plugin or via a manual installation. For manual installation, you just go to its home page (www.scala-lang.org/download/) and download the latest version (2.13.0 at the time of this writing). Uncompress the downloaded file in your directory of choice and configure it. Let’s install Scala via the plugin, as shown in Figure 9-12.
Figure 9-12. Scala IDE plugin installation for IntelliJ IDEA 388
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
OK, that was your introduction to Scala! It was a very simple introduction, we know. However, as we said, covering the language in depth is outside the scope of this book and there are a lot of great books dedicated to the topic. We’re assuming here that you probably know the language better than we do and you are just interested in how to use Spring Security with it. The next thing you’ll do is create the project by combining Scala with Spring and Spring Security. See Figure 9-13.
Figure 9-13. New Scala project Convert the Scala project to a Maven project, and in the generated pom.xml file, add the Scala dependency shown in Listing 9-29.
389
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
Listing 9-29. Scala Maven Dependency org.scala-toolsmaven-scala-plugin2.15.2 Next, of course, you need to add the Spring dependencies to the pom.xml file. You should know how to do this by now. You also need to add the Scala plugin in the plugin sections. In the end, your pom.xml file should look like Listing 9-30.
Listing 9-30. The pom.xml File in the Scala Maven Project <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www. w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0com.apress.pssscala-examplewar1.0-SNAPSHOTscala-example Maven Webapphttp://maven.apache.orgscala-tools.orgScala-tools Maven2 Repositoryhttp://scala-tools.org/repo-releasesscala-tools.orgScala-tools Maven2 Repositoryhttp://scala-tools.org/repo-releases 390
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
Integrating Spring Security with Other Frameworks and Languages
scala-test-compileprocess-test-resources testCompile This will be a simple web application with Spring MVC, and it will have a very simple Service layer. The big difference from the applications you saw in other chapters is that both the Controller layer and the Service layer will be written in Scala instead of Java. We’ll show you how to add security to the method level, but, as you’ll see, it is almost the same as with Java. Let’s start with the code for the controller. In the package com. apress.pss.scala.web in a source folder with the path src/main/scala, create the ScalaController class shown in Listing 9-31.
Integrating Spring Security with Other Frameworks and Languages
@RequestMapping(value = Array("/scala"), method = Array(RequestMethod.GET)) def scalaRequest(request:HttpServletRequest, response:HttpServletResponse) = { val value = service.scalaService response.getWriter().write(value) } } In the controller, you are defining a simple method that returns (writes on the response, actually) whatever the service returns. Note the use of the annotation @RequestMapping in the example. With Scala, you cannot use a simple string to set the value of array-based annotation values. You need to set a real array with just one element as the value. For the rest, the example is very much like the Java version, with some syntax modifications. Then, in the package com.apress.pss.scala.service, create the ScalaService class shown in Listing 9-32.
Listing 9-32. ScalaService Class package com.apress.pss.scala.service; import org.springframework.stereotype.Service import org.springframework.security.access.annotation.Secured trait ScalaServiceFacade { def scalaService: String } class ScalaService extends ScalaServiceFacade{ @Secured(Array("ROLE_USER")) def scalaService() = "Service accessed" } Again, the code in this listing is straightforward. It is a service class with a simple method that returns a string. Notice how you are using the @Secured annotation here. Again, you are using the Array function (actually, what you are indirectly calling here is the apply method of the Array companion object, which allows you to create a new instance of the Array class) and passing the string that will be the only element of the 394
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
array. With Scala, you can’t use the convenient technique of passing a simple string for this value as you did in the Java version. Note also the use of the trait just before the class definition and then the class extending that trait. A trait in Scala is somewhat the equivalent of the interfaces in Java, but it has a lot more power. Although not shown in this example, a trait can have fully implemented methods as well as the traditional abstract (definition only) methods typical of Java interfaces, and your class can extend more than one trait “inheriting” the functionality defined in all of them without needing to implement the already implemented methods. This technique, also called mixin, is in no way exclusive to Scala because other languages also have constructs that fulfil the same purpose. For example, in Ruby you can use modules to achieve more or less the same outcome that you get with Scala traits. Again, we won’t go into any details about this. For this example, you can think of a trait simply as a Java interface that you use from the controller to access the service. Next, you need to make your configuration. By now you should be very familiar with configuring a Spring Security Web Application. So we will simply show you the files next and won’t go into the details of any of them. Note that we are referencing Scala classes just as we used to reference Java classes before in our beans. It is great that interoperability between Java and Scala is so nicely achieved. Listing 9-33 shows the web.xml file. Listing 9-34 shows the applicationContext-security.xml. Listing 9-35 shows the file scala-servlet.xml. All files should live under the WEB-IF directory of your application.
Listing 9-33. The web.xml File for the Scala Project Spring MVC Applicationscala
395
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
Integrating Spring Security with Other Frameworks and Languages
Listing 9-34. The applicationContext-security.xml for the Scala Project 397
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
Listing 9-35. The scala-servlet.xml File with the Controller Definition Now you can exercise your project. From the root of the project in the command line, execute the command mvn clean install jetty:run to run the application. It should run without any problem. Next, if you try to access http://localhost:8080/scala-example/enter/scala, you’ll be presented with the login screen you have seen so many times before. If you log in with the username luna and the password nardone, you should be able to access the application and see the page shown in Figure 9-14.
Figure 9-14. Successfully accessing the secured Scala application
398
Chapter 9
Integrating Spring Security with Other Frameworks and Languages
That’s all we are going to say about integrating with Scala. You can see that for a simple application like this it’s not really that different from integrating with Java itself. Actually, beyond the syntactic differences, it’s probably just about the same. Of course, in this example you are starting with a Spring application and defining the different components (controllers and services) in Scala. It could be more of a challenge to integrate if you are using a Scala-specific framework. All you need to remember is that to secure methods, those methods need to belong to Spring-managed beans. To secure URLs, the URLs need to be accessed in a servlet-based web application.
Summary In this chapter, we showed you, at a fairly high level, how to integrate Spring Security into various frameworks and languages. The chosen framework was Spring’s own Spring Web Flow, and the illustrative languages chosen were JRuby and Groovy, with their web frameworks Rails and Grails, respectively. We showed you that integrating Spring Security into these different frameworks and languages can be straightforward (as with Grails) or not so straightforward (as with JRuby or Scala). However, through the JVM, in theory, you could integrate Spring Security into anything you need to secure. Here is the main takeaway from this chapter, regardless of the frameworks or languages used: Spring Security is, ultimately, just a Java library (a couple of simple .jar files). You can integrate it into any Java (as in JVM) project that you want. You simply have to remember what you can do with it and use the parts that make sense for your particular problem. For instance, using web-layer security wouldn’t make sense in a Swing application, or even in a web application that doesn’t use standard servlet filters (such as the Play framework).
399
APPENDIX A
R eference Material This appendix contains reference material for Spring Security v5. Tools utilized in this book: •
Java SE Development Kit (JDK) 11: www.oracle.com/technetwork/ java/javase/downloads/jdk11-downloads-5066655.html
•
Maven 3.6: https://maven.apache.org/download.cgi
•
Spring Security 5.1.5 release: https://github.com/spring- projects/spring-security/releases/tag/5.1.5.RELEASE
•
IntelliJ IDEA 2019.2: www.jetbrains.com/idea/download/#section= windows
•
Apache Tomcat Server and plugin v9: https://tomcat.apache.org/ download-90.cgi
Spring Security official web page: https://spring.io/projects/ spring-security
•
Spring Security official documentation web page: https://docs. spring.io/spring-security/
•
Apache 2 License (Spring Security is an open source tool released under the Apache 2.0 license): www.apache.org/licenses/LICENSE2.0.html Spring Security on Twitter: •
@SpringSecurity
•
@SpringCentral
•
To ask and check questions related to Spring Security: https://stackoverflow.com
Spring Security example references:
402
•
https://spring.io/guides
•
www.springboottutorial.com
•
www.tutorialspoint.com/spring_boot/
•
www.baeldung.com/security-spring
•
www.javacodegeeks.com/spring-security-tutorials
Index A AbstractSecurityInterceptor, 75, 77, 78 Access control entry (ACE), 224 Access control lists (ACLs), 6, 18 accessing secured objects AclEntryVoter, 256, 263 custom AccessDecisionManager, 258 deletePost method, ForumController, 259 deletePost method, ForumServiceImpl, 259 cost, 275 filtering returned objects classes and interfaces, 271 classes participating in post-processing phase, 272 createPost method, 267 ExpressionHandler bean, 265 getPosts method, 264 @PostFilter annotation, 271 @PreFilter annotation, 270, 271 @PreFilter invocations, 271 PreInvocationAuthorization AdviceVoter, 264 steps to execute new configuration, 268 securing view layer, 273 security example application abstractions, 224 acl-example-servlet.xml, 250