About this talk
This talk covers the new features introduced in Spring Security 7, with a focus on multi-factor authentication, modular configuration, and updates to OAuth 2. The speaker, a member of the Spring team, describes significant changes such as the deprecation of the OAuth 2 password grant and the introduction of an easier lambda-based configuration approach. Multi-factor authentication is presented as a major enhancement that has been in demand for years, allowing applications to require multiple forms of verification before granting access. Additionally, the session explores how modular configuration simplifies security setup in Spring applications by allowing developers to customize only the necessary parts of the security filter chain without overriding defaults. The talk also touches on updates to the authorization server, including the enforcement of PKCE (Proof Key for Code Exchange) and the option for dynamic client registration.
Full transcript
[music] >> Using my left hand here. Um, I did break my arm on Sunday, uh, my shoulder to be technically correct. So, I cannot tie my shoes. I cannot tie my hair, but I can code. Lucky you, eh? I work in the Spring team. I work on security stuff and these day everybody is doing AI, so I am working on Spring AI and MCP for Java. Here's
all my contact information. The reason I come here, um, is because I want to meet you. I want to learn from you. You know, I want to show you a few stuff in the process, but I really want to learn what you're doing, how you're using our projects, what are the the pain points and how we can improve them. Speaking of breaking my arm, when I shared
this, uh, on social media lately, um, Sergi answered. So, I say, "Oh, I broke my arm." And then Sergi said, like, "Ha, ha, easy. I did that last time, right?" So, if you want to speak at Spring I/O next year, you know what you have to do. Maybe don't do that. I don't know. All right. So, Spring Security 7, I have a ton of content. It's going
to go fast, not much time for question during the presentation, but after I'll take I'll take all the questions outside for 2 days. If you want to go over what's new, by yourselves, then you can go on the on the what's new page. Incredible, right? So, in here here's the list of all the things that we thought were new for this, um, release. There's also a migrating
to 7, um, section in the guide. So, you can go for specific applications if you have things that don't compile or that behave weirdly, go take a peek here. You know, your SAML 2 default configuration has slightly changed, you can go and take a look at that. But, I want to highlight the new stuff. Hey, who's that guy? Uh, I want to highlight the new stuff that
we have, right? The reason you're here is to see some exciting things. So, I'm going to go over the boring stuff first. And then we're going to go the over the biggest feature that's been delivered in this release, which is multi-factor authentication. Uh, then we'll take a look at modular configuration, what that means and how it improves the configuration of your Spring Boot applications that have security
in them. Uh, we'll look at OAuth 2, uh, new features on the authorization server side and on the client side. And maybe maybe at the end, if I have a little bit of time, I'll cover MCP security, or at least I'll point you in the right direction. So, boring stuff first. Uh, we've changed some modules. So, anybody here using the old access decision voter manager, anything? Yeah,
right? A few of the So, that has been deprecated, uh, since the end of version 5. Now, the infrastructure has moved to a new authorization manager infrastructure. You should look into that. We didn't want to punish you for not moving away, so we kept these classes, but they're outside of the core now. So, in Spring Security 7, they're in a new package called Spring Security Access. Um,
Kerberos. Anybody here is unfortunate enough that they have to use Kerberos? One, two, three. Oh, wow, quite a few people. Uh, so we brought this into org.springframework.security. It was its own group ID before and we aligned the versions with Spring Security. So, we jumped from 2.something to 7.0. Same goes for Spring Authorization Server. Uh, it was its own project. The coordinates are still the same, so it's
still, you know, Spring Security OAuth 2 Authorization Server. The difference is that we changed the version from 2. something to 7.x. These two last things, um, Kerberos and AuthZ Server, it it's mostly for our convenience to release these packages, uh, more easily. Now, we release everything together. We have the same number, we don't have to think about it. We have a single release process. It shouldn't change
much for Um, we removed a few things and I'm I want to start by the thing that you like the most. Um, we removed OAuth 2 password grant in the client. Anybody using OAuth 2 password grant? One, two. And you're not ashamed of it, that's good. so I know I know I will make you sad. I know a lot of people use this. I know it makes
people sad because people have been asking for OAuth 2 password grant in the authorization server, which we have never implemented for a reason, right? I know it makes you sad, but the reason is if if you go to OAuth 2. or off.net and you go look at the recommendation, they will tell you the password grant is a legacy way to exchange username and password. So, you pass
that to a client application, the client application uses that to get a token on your behalf. And so, since you're passing your password to a client application, it is not recommended that this thing be used at all anymore. Right? So, you should not be using this, that's why we remove it. If you are in an extremely high trust environment and you know what you're doing and you
really want to use this, you can re-implement it from the old classes we have. It's not a big lift, it's like three, four classes. But, the real thing that you should do if you're using password grant is move to authorization code. We have all the infrastructure for that now, for single page apps, for back-end apps, for everything. So, that's gone. Sorry. Another thing that's gone, it's been
it's been making for a while. So, you remember the classic, uh, Spring Security DSL with the magic keyword and, right? And you had this blah blah blah and and and, it's gone. Um, so we deprecated this at the end of five, at the five line, and we introduced the lambda configuration. So, instead of using, uh, this end thing, you can't do this anymore, it doesn't compile. You
have to use lambdas. It's very easy to migrate from one to the other. You take this is a configurer that configures authorization for HTTP request and this is the basically the closing brace, the end here. So, you take everything that's between the configurer and the closing brace and you put it inside the lambda. Right? Should be fairly easy. I think tools like Open Rewrite will do that
for you automatically, so you don't have to think too much about it. Boring stuff out of the way. Now, let's get to the exciting new things. Multi-factor authentication. I am very excited because I think it's the longest awaited feature in Spring Security of all time. Um, if you look at the issue number 2603, I think today the issue numbers are in the 19,000. Right? So, we can
go take a look. And this issue was migrated from Jira on November 8th, 2013. I wasn't even born by then. And it was closed September 24th, 2025. Less than 12 years. See, we can go fast when we want. All right. So, let's see what this looks like. I have an existing application And this application, is it running? It's not running. Let's make it run. Run. Come on.
Wake up. Here we go. Voilà. First try. So, this is a basic Spring Boot application. I have a bunch of pages already. It has a public page, so I can go publicly. It has a private page. To go into the private page, I have to log in. And I implemented two ways of logging in, password, username and password, and one-time token. One-time token is a fairly recent
addition. We did that 2 years ago, I think. So, the way this is configured, let's do presentation. Here we go. So, you have a configuration class, usually a security configuration or something. You create a security filter chain in which you do your authorization rules. Right? So, this says, "Oh, there's some public pages here because they're permit all." And then there are some admin pages only the admins
can see and the rest you must be logged in. And then you can say how can you log in? So, you can log in with form login, username and password, and then you can log in with one-time token. Right? So, with this I can log in DanielPW, right? And then I'm logging into the page here. Uh, and the one thing you'll notice is I when I look
on that page at the details of the user who's logged in, so username, password, authentication token, but when I look at their authorities, so the thing that we use for permissions, they have a role and the role is user, so Daniel is a user. And they have a new type of authority, a factor granted authority. So, this is a simple granted authority, it's just a string with
a prefix that says it's a role. But, this represents the things I used to log in. So, in here I used a password and it tells me when this happened. So, this was 40 41 UTC, right? So, 1 minute ago, something like that. And using that, we can now make authorization decisions saying, "Ah, you must you must have more stuff to access this page." And the way
you do it is your configuration class. Is the font size okay for everyone in the end, in the back of the room? Okay, yeah, okay. So, in here you say enable multi-factor authentication. It requires an authorities, uh, uh, parameter. And so, in here you can say which factors you require. And so, factor granted authority.password and then factor granted authority.one-time token. And so, with these two things, now,
if I try and access the private page, I get redirected to the login page again, but with some details. It tells me here, "Well, you must log in with the factor type OTT, one-time token, because it's missing." Right? I only logged in with my username and password. So, now I must do OTT, so let's send a token. I get an email here with my token and then
I I log in. And now I'm able to access this page. in the details of the authentication of the user, there's two factors. There's one-time token and password with different timings, right? I logged in at 14:41 on this one and 14:43. 2 minutes later I got my one-time token. So with this this thing uh applies a site-wide uh authorization mechanism. But maybe that's not what you want.
Maybe you have have uh you know, the private page is fine. Anyone with a simple authentication can access it, but maybe for, say, there's an admin page and I want admins to have two factor. And so you can't do it like this. You have to act at the uh endpoint level. So in here in this authorize HTTP request. And before we go into how you actually do
it, I want to rewind and kind of explain how this works. All of these methods permit, has role, authenticated, and so on. They all under the hood wrap the method that's called access. And access is the the API we give you to make complex authorization decision. For example, in here I have a Daniel page, {slash} Daniel. And I want that page to be accessible by Daniel only,
right? So we could do something like uh off z.requestMatchers {slash} Daniel uh dot and I don't have a uh for example, is Daniel. That doesn't exist in Spring Security. I tried, but the others don't want it. So, the way you do it is you do dot access and in here you pass an authorization manager, authorization manager of request context. This authorization manager can be uh expressed with
a lambda. So there's an authentication supplier. Supplier. Um and then a request context because here we're doing authentication in the context of an HTTP request. And from that you must return a new So by default I deny access to this page and then I can look at the authentication. So the the user who's logged in. So I obtain it from the context and that gives me an
authentication. And so if the authentication is not null, the user is logged in and the name is equal to Daniel equals ignore case to authentication.getName then in that case, yes, this is Daniel. I am allowed to view the page, return new authorization decision of true. All right? Something like that. So now uh if we go say Safari and we say localhost 8080, go to private, login as
Josh. Here we go. Need a token. Fine. Take the token. Go back to Safari. Sign in, right? And then Josh is not allowed to see the Daniel page because Josh is not However I am Daniel, so I can see the Daniel page. Yes, I'm Daniel. That's fine. Okay? if you want to do complex multi-factor authentication setups, you're going to use this access thing. So how do you
use it? So let's go back. We have our uh enable multi-factor authentication and I say, well, I don't to enforce these site-wide. I want a special endpoint. So let's say that the admin endpoint here right? This has role admin. I want this one to be covered by MFA. So how do I do it? Well, I can do a authorization authorization manager factory. Right? This type is a
thing that creates authorization managers. And so which one is it? It's the multi-factor one. And so it requires factor uh we said factor OTT and then factor and then we build and then this is uh call it MFA factory or just MFA for short. And so in here instead of using has role we say access MFA.hasRole admin. Right? So it's admin with MFA. Both together. So this
is a factory. Okay? So that means if I go back to my main app, now as Daniel right? I can access it with just my password. This is fine. But if I'm Josh and I log out right? Josh PW, right? Josh can see the main page. Josh is admin. He wants to see the admin page. Now I can trigger and say, ah, here you need a second
factor. If you had it already, I would let you go through, but in here need to log in again. All right? So log in. Here we go. And then, you know, Josh can see the admin page because he's admin. Daniel wouldn't be able to see it because he's not admin. So that's for um MFA on a specific endpoint. But with MFA you can do more interesting stuff.
Right? You can do a endpoint, but you can also do I want um my my my login to be recent enough. I don't want you to log in 3 days ago and be able to do privileged operations with that login from 3 days ago. I want you to refresh the login. So in that case, you can do a different authorization manager. Uh in that case, it's an
all required factor Okay? This is This is a factory. So with this factory you can combine this with has role, authenticated, etc. This one is a direct authorization This is an off z manager. So this one you cannot easily do has role on it. Okay, we say this one and then require factor, required factor builder, and then [snorts] we say maybe for the password uh I want
that password to be valid. And so I want this to be a duration of seconds, 3 seconds. Build. And this is a recent password. So with this, we can say for the password page, you must be logged in at least with your password. It's fine if you have OTT as well. And your password must be you the authentication that you obtained with your password must be at
most 30 seconds old for the demo, right? And so in here we could do off and we want the password page and then we want access again, right? And then in here we say recent password. Like this. Right? And so now the main page stays the same. I'm logged in. I'm logged in with my password, but this was at 47:36. It's now uh 4 50, right? So
it's like 3 minutes ago. So now if I go and say update password, I navigate to the password page then an access denied exception is triggered and handled. I get redirected back to logging in with a password because I need a password. And it says, well, there's a error. The factor is password and the reason is it has expired. Your authentication is too old. So I can
log in again with my password and then I can access the password change page, right? Like this. And then you can do more complex stuff with MFA. It's a lot to show. I'm not necessarily going to show everything, but basically this access, you can use it to combine rules together. So you can combine them using um utility methods we have. For example, we can say authorization managers
uh all of and then uh authorization uh any of and then maybe you have a recent password, right? Or you have authority authorization manager has role. So either you have a recent password or you're admin. So can combine these and get this gives you a manager and so you can use that. Or you can even write your own lambda in here, right? You can write and say,
oh, if the role is admin, then I want MFA. If it's not admin, then I want just a uh password login. It's fine, right? So take them together, combine them, and then do all your authentication rules. I'm wrapping up. I know, it's a lot. Um so MFA built around the new factor granted authority, which is the special authority that represents your login, when you logged in, how
you logged in. You use the @EnableMultiFactorAuthentication to scaffold the the infrastructure that you need to do MFA. And then it's all around the the the concept of this authorization manager factory that creates that has role method in with an extra layer of MFA on top of it. So you can use it for your entire application with enable authentication with a list of authorities or on a given
endpoint, so you you will give you a a authorization manager for that endpoint. Uh and the the utility method we have for building those is all required factors. Authorization manager Java naming is fun. And little example we had uh uh when the authentication must be recent uh with this all required factors authorization manager, you can say I want password and I want it to be that recent.
Be careful. Don't do 30 seconds, right? Cuz if you're if you have two systems running in parallel and their clocks skew by a minute, then this is going to be a problem. But typically you do like, oh, I want a password that's like 5 minutes old or something. And then you use it by saying blah blah blah, my endpoint.access. the thing we've created. Okay. I mean, oof,
I'm ahead of schedule Um so MFA great. Go check it out. go try it out, come with your feedback on the issue tracker. The second thing different that we've introduced is modular configuration. So, when you configure a security for a Spring application, uh typically you do what we did here. So, we create a bean of type security filter chain. The problem with this, uh we can see
it in action. So, I have a second app running, an authorization server here. So, if I go to localhost 9000, this is an auth server, And this auth server, see it it asked me to log in and then it has endpoints, right? Well-known open ID configuration, not HTTPS, just HTTP. There we go. So, it has this, you know, description of what this auth server is doing, right?
Now, say I want to change this configuration because I want the page that's called public.html to be public, right? But it's not public. All right, so okay, I'm going to go and do this. So, we go to the authorization server, there is a configuration class of some kind, maybe the Spring Boot application, just like Josh does, and then we do a security filter chain, let's have C,
takes HTTP security, HTTP, and then um we said we want the public page to be public. Return HTTP.build, and then maybe we say, okay, we want authorize HTTP requests, uh auth Z, auth Z.requestMatchers public.html, maybe let's also put the error page in case something goes wrong, permit all, and then maybe we also say anything else, oops, sorry. Nope, wrong, wrong. Okay, auth Z. Uh any request to
authenticate, right? So, in here, my public page now should be public, meaning if I go here and I say localhost 8080 public.html, uh not found. Maybe, maybe. public.html, did I type it right? Oh, it's the wrong server, 9000. Yay, it's a public page, right? So, this one is a public page. Now, if I try to go to private.html, I get a 403. I don't get the form
login that I should be getting. And the problem is if here I say well-known open ID configuration, I get HTTPS errors, fine. I also get a Why is that? It's because we've produced a bean of type security filter chain, right? So, whoops, I smashed Spring Boot's defaults. Scream. Right? I want Spring Boot defaults. I just want to change this tiny little So, in here I would have
to go and find, oh, what what do I need? I need form login, I need authorization server with certain customizations and so on. So, we provided a a new way of configuring that. Instead of providing an entire security filter chain, you're going to provide a customizer of HTTP security. Right? An HTTP security customizer, this is a lambda, takes HTTP, returns whatever you want, void actually. And so,
in here you can do your own changes. So, you could say HTTP. authorize HTTP requests, auth Z, fine, auth Z.requestMatchers all. And so, that's the only change I make, and the rest is boot defaults. So, now if we go back here, after a little while it restarts, the authorization server defaults are present, the public page, my change, oops, this one, still public, right? And if I try
and go to say foobar, then I get form login because it's a private page and I need to log in to see it. So, I've only changed a little bit of behavior while keeping boot. This customizer can apply to other things. Basically, anything that is under http. So, HTTP um http. Uh say OAuth2 authorization server or HTTP, your your favorite one, CSRF, right? Stuff like that. All
of these, they apply some changes on a configurer, OAuth2 authorization server configurer, or CSRF is a CSRF configurer. So, all of these configurers, you can modify just that bit as well by providing a bean, oops, bean of type of that configurer. So, OAuth2 authorization server configurer, there Uh auth Z configurer customizer, auth Z And so, in here I can I can only change the defaults for the
authorization server. So, auth Z server, and then let's say we want to change the the page that we see here with the claims. So, that's the OIDC. In OIDC, we want to change OIDC.provider endpoint configuration endpoint. So, config, and this one has a claims customizer, provider config.provider configuration customizer. So, that's the config, and in the config I want to add a claim, claim as a conference spring
IO 2026. There we go, right? Okay, and so with this I only changed the authorization server. I don't act on the entire HTTP security. So, that means when I go here, that's beautiful. When I go to the provider endpoint configuration, I have a new entry, that conference, right? So, I've only changed the behavior that I care about at my top-level configurer for authorization Okay. So, modular configuration,
and now you don't need to provide an entire security filter chain. Now, I actually avoid doing that unless you want to change boot defaults. Boot defaults are usually pretty good for what you want to Instead, provide a customizer for either HTTP security or for say CSRF configurer, session management configurer, and so on. And if your customizer has exceptions, we also have a throwing customizer variant, right? So,
if you need to reach out to some external service to compute something in your customizer, then you can do a throwing customizer. Okay, so we have MFA, we have modular and so we have a bunch of new things with OAuth2. So, on the OAuth2 side, there are two two things we changed. There's the and there is the client. So, on the authorization server, the first big change
that you might encounter if you have clients that are not Spring Security 7, is that we are now enforcing PKCE, Proof Key for Code Exchange. So, in the configuration of my authorization server here, um if my clients have issues and they're not able to support Proof Key for Code Exchange, I need to set this to false, require proof key to false, default to true. So, basically, this
is uh aka PKCE. And this is when a client makes an authorization request, they send a code challenge of some value, and then when they make a token request, they send a code verifier parameter. So, if you see an error saying blah blah blah missing code challenge, like uh parameter error code challenge, that's because the authorization server wants your clients to send code challenge. If they don't,
you can turn those default this behavior um to you can turn it off if you want. Um it's recommended that you leave it on. Even this was meant it for public clients, so clients say on your Android device that cannot store a secret uh efficient or securely, uh but it's the also now recommended for private client. So, if the ecosystem you work with doesn't support it, you
can always turn this off fairly easily The second thing we've added is dynamic client registration for uh authorization OAuth2 authorization server. So, you can turn it off turn it on, sorry, by saying uh client registration endpoint, right? This customizer here. And the the thing that it does is it allows you to register clients using uh a formalized spec. Uh so, in here there's the OIDC endpoint, there's
also a special endpoint called OAuth authorization server that describes the OAuth2 features of my app, right? So, it has a bunch of endpoints and so on. If I turn on client registration endpoint here with defaults, after a little while this refreshes, and we get a new endpoint exposed here, the registration endpoint. So, if I want to register a new OAuth2 client, so beyond what I have in
in here, application.yaml, beyond these, or beyond what I have in my JDBC register client repository, I can now use this endpoint. So, to use it, in here if I do HTTP 9000 uh OAuth2 register, and here I need to pass the details of what I'm registering. So, this is going to be say uh client that has grant types, we're going to support authorization code, right? And then
maybe also client credentials. Who knows? Okay. And then redirect URI, since we don't since we do support uh authorization code, so maybe I don't know, my client lives at https example.com/oauth2/callback for the redirect URI. Fine. Something like that. And so, if I do this and I send that, I get a 302. Tells me, "Ah, but you must be logged in to do this." Right? It's not uh
it's not allowed for everyone. So, you must have a token to be able to talk to that endpoint. First, let's obtain a token. Fine. So, 9000 oauth2 token, and I'm going to use the client credentials grant type, so the machine-to-machine, cuz it's much easier for a demo in my terminal, client credentials. And I also need to request some scopes, so the permissions. And so, the scopes I
need here is what's it called? client.create. If you don't If your client If the client you use to make that token call doesn't have client create, you cannot register new clients. So, you have a kind of an admin client that is allowed to register other clients. >> [snorts] >> Uh and then you need to pass some credentials. So, in here I preset them to default client default
secret. Chef, and then I should get a response. So, here I have an access token. Fine. uh put this Can't see my cursor here. jq -r. dot access token. So, we only have the Yeah. Oop. No, not this one. Come on. Cursor. Auth. Well, I have the access token. I can expec- inspect it if I want, um but I'm just going to store it in here. Export
demo token equals this. So, now I have a token for my admin And so, I can do my uh client registration call from here that I did back in the in the beginning, and then pass that token authorization put in quotes authorization bearer demo token. And so, with this, I can register a new client. So, now there's a new client on my authorization server. The client ID
is randomly generated. I can use I can set it if I want in that request in here when I pass these things. >> And then client secret, and then the details that come with it. If I try to do this again, I get invalid token because I can only, using a given token, I can only register one client. And after that, the token is discarded. So, if
I want to register a new client, I must request a new token, and then I can Not everybody needs this. Actually, the only reason we implemented this, the only reason people cared about this is because of MCP, model context protocol. Because when you start, you know, your say cloud desktop or something, in here when you register a MCP client, you point it at a server, and then
it registers itself. So, that means you could have thousands of client registered in your um in your authorization server. Which, for this to work, this thing doesn't have an admin client. So, the fine folks that define the spec of um of MCP, they said, "Well, uh you know, anyone should be able to register. So, let's do uh client registration, and so we needed to add open register
and registration allowed." Okay. So, anyone can register, and then request matcher oauth2 register permit all, right? And so, now anyone can come in without a token, come in here, and then register a client and another and another and another and another and another and give me a denial-of-service. so this, personally, I think probably don't do this. Right? In fact, they agree with you with me, and now,
since now we've implemented all of this, they said, "No, no, no, that's the the old way of doing things. Uh now we're going to do a a completely new spec." It's possible. Do it I you at your own risk. You should probably do a little bit more infrastructure to verify who registers clients. So, don't do open force uh clients to be present. So, that's for the auth
server side, Pixie client registration. For the client side. So, for the client side, I have a third application that runs on port 8090 here. It's a oauth2 resource server, and it has you go and say winners from 2022, and probably need quotes around this. So, if I try and get that from my authorization server, I get http 401, right? Unauthorized. And says, "Oh, you must authenticate using
a a token." All right, fine. So, I'll get a token. Export demo token. Cool. And I do the same request here, and I say $ demo token, right? Okay. So, I have a server, and it serves resources if I have a token. Now, let's use that in a Spring application, in a client application. So, there I have a Hugo service here, and it's a component, and it's
used in a controller. And so, I could use a rest client, and then I can do oh rest client.dotBuilder, and then I probably need to pass a request interceptor, which is an oauth2 blah blah blah request interceptor that needs a oauth2 authorized client manager, and yeah, complicated. Also, this just points at my authorization server. It doesn't really need to be a rest client. I don't really care
about it. So, what's really cool with Spring Framework 7 is that you can use http service clients. I can turn this component into an interface. So, not a class anymore. Interface, single C. All right. I make this a method of my interface. I say this is a request param that's going to be passed, and I say this is a get exchange, and it's going to get some
resources at http localhost 8090/Hugo. Let's get the nominees. And then let's say uh that's it. The year is is going to be as a param. it needs to be available for others other services to consume. So, maybe in any configuration class of my application, I declare this is a service client. So, MFA application here. And I say import http services, and then I import Hugo service. dot
class. And now, with this, I could be able to go to localhost Hugo. This is going to use that component, and it should fail. Uh not Hugo. Oh, yeah, oauth2 Hugo. I think I called it oauth2 Hugo. This should fail because So, I get a 500, right? The same point doesn't work on my client because the client is not sending a token. The http service client is
not sending a token. That's why we had the request interceptor for the rest client. So, instead, we can configure the service client to use a client. So, there's two things you need to do. Um of course, you know, uh th- this is beautiful beautiful Java naming as well, right? So, you must provide a bean, and the bean is of type oauth2 rest client http service group configurer.
Lol, Java naming, right? And so, we return oauth2 import it. Fine. oauth2 thingy.dot from oauth2 authorize oauth2 authorized client manager, right? So, this is the infrastructure that allows fetching tokens. it needs to know which client to use to get tokens because in here in my client application, I declared two Spring Security oauth2 client registrations. I have my admin client. I also have a default client to get
bland tokens that can't create stuff. So, in here I say client registration ID is uh called default client, we said. And with this, after the app restarts, we get default client blah blah blah blah blah blah blah blah. Let's restart the application one more. And is the name correct? Default client. do do do do do do do do Client registration ID. Here we go. Oh, no. Oh,
I know. Of course. There's one more thing I need to do. Does anybody know what what could be missing from here? So, in here, it's Spring Security that's giving me a client authorization required exception. This is an internal exception that says, "Go fetch a token." I'm missing the infrastructure to go fetch a token. So, in here, I need to say I want oauth2 client with defaults, of
course. Otherwise, it doesn't catch the exception, doesn't go fetch a token, nothing works. Uh right? And so, in here, I got redirected to localhost 9000, my authorization server. I log in with Daniel SSO. Uh cool. And then boom, first try. So, with this, now, instead of having to configure your rest client, you know, point at the right things, put the encodings, and so on, you can use
the new um http service clients from Spring Framework with this and a bean, and that's it. The rest, the clients in application.yaml and the oauth2 client, you already had it in the past for the Okay. Right. So, new things, authorization server, Pixie. So, if you have things that tell you, "Oh, there's a problem with code challenge." then probably the clients don't can't use Pixie and you want
to turn it off. Uh and dynamic client registration for OAuth2. And on the client, we support HTTP service clients. The new things you need to do is add client registration ID on the method or on the class and the famous OAuth2 REST client HTTP service group configure, which you can apply further customizations to. uh we have time for MCP security. Beautiful. MCP model context protocol. Anybody uses
model context protocol Yeah, maybe a shy 10% 50% of the room. Um so, we created a project to track this uh in Spring AI community. So, it's not in Spring Security because these guys, they change their mind every 6 month. And uh Spring Security, we work on the security thing. You know, where the thing where we we get to there, we do a pre-draft and it takes
5 years and then we put it as a draft and then 12 years later, we have an RFC. That's not the the way startups and AI works. Uh so, that is not going into core Spring into core Spring Security, but it's the people from Spring Security. It's me and Joe who work on It will eventually, once they stabilize on what they actually want from MCP, it will
go into Spring AI itself. This is where it will live. So, you can go there. Um you know, there's a bunch of documentation. The way you use it, you've seen Josh do it this morning, maybe really fast. You go to start.spring.io. Right? And so, in here, you say I want say an MCP server, right? And with this, you can say I want MCP security. So, it's marked
experimental cuz it moves really fast, even faster than than the rest of the AI ecosystem. And so, when you do this in your Gradle or POM, if you don't want to be too scared. Uh in your POM, you'll have the Spring AI dependencies. So, Spring AI starter MCP and a MCP server starter, right? If you change and you say, "Well, no, I don't want to secure my
MCP uh server. I want to secure my MCP client." You just change this to client. You keep the security bit and if you have this, we will put the oops, the MCP starter, the Spring Boot AI starter MCP client and the correct starter for the client. And because MCP, they they want everything to be included in their vision of the world, the authorization server as well. Because
MCP security is by is based on OAuth2 and it wants features that are not released anywhere. So, there's this new client ID metadata, I think. there we're starting to see releases in the wild, but nobody supports this, right? So, we do more stuff so you can enhance your authorization server to use that. And in general, if you want to see kind of in detail how it works,
uh let's close these. I'm going to run the samples from that MCP Security project. Open recent project MCP Security. This window. So, in here, you know, there's the code, but you have a bunch of samples. If you want to see how to configure them, usually it's very very small. It's a like kind of a Spring Boot starter. So, start the authorization server. I'm going to start just
the MCP server. And so, the MCP server, if I call it HTTP 8090/mcp, it tells me 401 because I need to pass a token, but it also exposes this resource metadata endpoint. And so, this is what MCP uses to discover where is the authorization server, what should I pass and so on. And if you want to see it in in real life, you can run this model
context protocol inspector project, which is a client for MCP. And if it starts, let's connect from this. Do do do. Come on. 1 2 3 Yeah, I know I'm not connected. All right, third time's the charm. Here we go. First try. All right. So, this is a thing for interacting with MCP servers. And if you want to see what happens, so you put the URL of your
MCP server here and you can go to open off settings. It has a magic quick off flow that does everything for you, but if you want to see the details, you can go to guided off flow here. And so, it shows the different steps that must happen. So, it says, "Oh, first I do metadata discovery. So, I'm going to talk to the uh MCP server and I
get this data about it." And in there, it points me to the authorization server. So, then I call the authorization server. The authorization server has a registration endpoint at some some point somewhere in here, registration endpoint. Okay, cool. So, now the client knows it can register a client. Boom, client registration happens. Here's the client that you get back. Okay, then you do the token flow. So, let's
go here. Uh Daniel Erno is here. It's user. Uh pass. I have a safe password in here, which is password. There we go. Get an authorization code. Pass the code. Get the token request. Authentication token. Token complete. I have my tokens. I can now connect, right? And so, now your uh MCP server is secure, can get the tokens, can use like it finds the username inside the
token and then All right. If you want to learn more about this because it's an entire talk, you can go to an entire talk about it tomorrow at and they will be covering security. Track 3, it's going to be fun. Go there. And with that, if you want the code for this presentation, it's over there. Take your time. Take a little bit of a picture. If you
have time, come chat with me. Like give me feedback and how I can improve this talk. Probably speak slower, maybe. Uh if you have questions, if you have projects you want to show me, reach out. And and and if you're too shy, you can reach me uh on by email, Blue Sky, so on. And I have one last surprise for you. I am not busy enough, so
I'm also writing a book about testing Spring Boot applications. It's in early access, so the first four chapters are online right now. You can go and get them. Uh they gave me a discount code. It's valid for 2 weeks on the entire site of Manning, the publisher. So, go wild. There's also a book by Craig Walls uh who which is great about Spring AI. There's a book
There's the the famous Laurențiu Spilcă Spring Security in Action there as well. So, you know, now's okay your occasion. And with that, I am sorry. I ran over. Sanderson is yelling at me. Thank you very much. See you at the party after. >> [applause]
More from this event
See all 38 talks →
Spring I/O 2026 Keynote
1:08:44
The Spring AI Ecosystem in 2026: From Foundations to Agents @ Spring I/O 2026
43:39
Breaching LLM-Powered Applications: Overcoming Security and Privacy Challenges by Brian Vermeer
48:40
Bootiful gRPC by Josh Long & Dave Syer @ Spring I/O 2026
33:48