I want to setup a spring boot application with 0-legged (so no request or access tokens) OAuth 1.0. I have been digging around for awhile trying to find an example and I am mostly stuck on how to configure things using the new style (without xml).
For now I just want to get a simple use case working where only 1 path (/oauth) is OAuth protected (everything else is just wide open) and it uses a custom ConsumerDetailsService (see below for the simple version of that code).
Here is my WebSecurityConfigurerAdapter (SecurityConfiguration.java next to my Application.java, which I think is the right way to configure this kind of thing in a spring boot application). I am pretty sure I am missing the provider configuration (as referred to in: http://projects.spring.io/spring-security-oauth/docs/oauth1.html) but my trial-and-error is not yielding results.
@Configuration
@EnableWebMvcSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 0-Legged OAuth on the /oauth and /lti paths only
http.requestMatchers().antMatchers("/oauth"); // .and().... what?
// ??? something must be missing here - provider?
}
}
I also have this in my maven pom.xml:
<!-- security and oauth -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- OAuth -->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth</artifactId>
<version>2.0.2.RELEASE</version>
</dependency>
My Custom ConsumerDetailsService
@Component
public class LTIConsumerDetailsService implements ConsumerDetailsService {
@Override
public ConsumerDetails loadConsumerByConsumerKey(String consumerKey) throws OAuthException {
BaseConsumerDetails cd;
// TODO really lookup the key and related consumer details, for sample here we just hardcoded
if ("key".equals(consumerKey)) {
cd = new BaseConsumerDetails();
cd.setConsumerKey(consumerKey);
cd.setSignatureSecret(new SharedConsumerSecretImpl("secret"));
cd.setConsumerName("Sample consumerName");
cd.setRequiredToObtainAuthenticatedToken(false); // no token required (0-legged)
cd.setResourceDescription("Sample consumer details - AZ");
cd.setResourceName("Sample resourceName");
} else {
throw new OAuthException("For this example, key must be 'key'");
}
return cd;
}
}
Any suggestions on how to get this working or pointers to spring boot OAuth 1.0 code would be greatly appreciated. Please note that I already tried looking at the separate spring boot security and OAuth guides and wasn't able to merge them successfully.
OAuth 1.0 only handled web workflows, but OAuth 2.0 considers non-web clients as well. Better separation of duties. Handling resource requests and handling user authorization can be decoupled in OAuth 2.0. Basic signature workflow.
To ensure you and your customers have a seamless experience, you'll need to move to OAuth 2.0 before OAuth 1.0a is deprecated. Partner and public apps have until 31 March 2021 to migrate, while existing private apps will continue to be supported until later this year.
Conclusion. Spring Security and Spring Boot permit to quickly set up a complete OAuth2 authorization/authentication server in an almost declarative manner. The setup can be further shortened by configuring OAuth2 client's properties directly from application. properties/yml file, as explained in this tutorial.
The Spring Security OAuth project has reached end of life and is no longer actively maintained by VMware, Inc. This project has been replaced by the OAuth2 support provided by Spring Security and Spring Authorization Server.
Here is how I got 0-legged OAuth 1.0 working in spring-boot 1.1.4 via Java Config.
NOTE: In my case I only wanted OAuth to protect a single path (/oauth/**) so if you want it protecting everything then you may be able to simplify some parts of this. You can see my complete code here: https://github.com/azeckoski/lti_starter
Once you have the minimal parts shown below you should be able to run your spring-boot app and fire an OAuth 1.0 compatible request at /oauth with ConsumerKey:key and Secret:secret and successfully load the path.
Important notes: (1) Do not just declare the ZeroLeggedOAuthProviderProcessingFilter as a Bean, if you do that it will end up affecting all paths (it will get picked up by spring automatically) (2) NoAuthConfigurationAdapter has to be there if you want to access the security data outside the protected path (in this case /oauth)
@ComponentScan
@Configuration
@EnableAutoConfiguration
@EnableWebMvcSecurity // enable spring security and web mvc hooks
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class Application extends WebMvcConfigurerAdapter {
final static Logger log = LoggerFactory.getLogger(Application.class);
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
// Spring Security
@Autowired
@Order(Ordered.HIGHEST_PRECEDENCE + 10)
@SuppressWarnings("SpringJavaAutowiringInspection")
public void configureSimpleAuthUsers(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password("admin").roles("ADMIN", "USER")
.and().withUser("user").password("user").roles("USER");
}
@Configuration
@Order(1) // HIGHEST
public static class OAuthSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
private ZeroLeggedOAuthProviderProcessingFilter zeroLeggedOAuthProviderProcessingFilter;
@Autowired
OAuthConsumerDetailsService oauthConsumerDetailsService;
@Autowired
OAuthAuthenticationHandler oauthAuthenticationHandler;
@Autowired
OAuthProcessingFilterEntryPoint oauthProcessingFilterEntryPoint;
@Autowired
OAuthProviderTokenServices oauthProviderTokenServices;
@PostConstruct
public void init() {
zeroLeggedOAuthProviderProcessingFilter = new ZeroLeggedOAuthProviderProcessingFilter(oauthConsumerDetailsService, new InMemoryNonceServices(), oauthProcessingFilterEntryPoint, oauthAuthenticationHandler, oauthProviderTokenServices);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/oauth/**")
.addFilterBefore(zeroLeggedOAuthProviderProcessingFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeRequests().anyRequest().hasRole("OAUTH");
}
}
@Order(45) // LOW
@Configuration
public static class BasicAuthConfigurationAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/basic/**").authorizeRequests().anyRequest().authenticated()
.and().httpBasic();
}
}
@Order(67) // LOWEST
@Configuration
public static class NoAuthConfigurationAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").authorizeRequests().anyRequest().permitAll();
}
}
// OAuth beans
public static class OAuthProcessingFilterEntryPointImpl extends OAuthProcessingFilterEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
log.info("OAuth FILTER Failure (commence), req=" + request + ", ex=" + authException);
// Called when there is an OAuth Auth failure, authException may be InsufficientAuthenticationException
super.commence(request, response, authException);
}
}
@Bean(name = "oauthAuthenticationEntryPoint")
public OAuthProcessingFilterEntryPoint oauthAuthenticationEntryPoint() {
return new OAuthProcessingFilterEntryPointImpl();
}
@Bean(name = "oauthProviderTokenServices")
public OAuthProviderTokenServices oauthProviderTokenServices() {
// NOTE: we don't use the OAuthProviderTokenServices for 0-legged but it cannot be null
return new InMemoryProviderTokenServices();
}
public static class ZeroLeggedOAuthProviderProcessingFilter extends ProtectedResourceProcessingFilter {
ZeroLeggedOAuthProviderProcessingFilter(OAuthConsumerDetailsService oAuthConsumerDetailsService, OAuthNonceServices oAuthNonceServices, OAuthProcessingFilterEntryPoint oAuthProcessingFilterEntryPoint, OAuthAuthenticationHandler oAuthAuthenticationHandler, OAuthProviderTokenServices oAuthProviderTokenServices) {
super();
log.info("CONSTRUCT Zero Legged OAuth provider");
setAuthenticationEntryPoint(oAuthProcessingFilterEntryPoint);
setAuthHandler(oAuthAuthenticationHandler);
setConsumerDetailsService(oAuthConsumerDetailsService);
setNonceServices(oAuthNonceServices);
setTokenServices(oAuthProviderTokenServices);
//setIgnoreMissingCredentials(false); // die if OAuth params are not included
}
}
}
@Component
public class OAuthConsumerDetailsService implements ConsumerDetailsService {
final static Logger log = LoggerFactory.getLogger(OAuthConsumerDetailsService.class);
@Override
public ConsumerDetails loadConsumerByConsumerKey(String consumerKey) throws OAuthException {
BaseConsumerDetails cd;
// NOTE: really lookup the key and secret, for the sample here we just hardcoded
if ("key".equals(consumerKey)) {
// allow this oauth request
cd = new BaseConsumerDetails();
cd.setConsumerKey(consumerKey);
cd.setSignatureSecret(new SharedConsumerSecretImpl("secret"));
cd.setConsumerName("Sample");
cd.setRequiredToObtainAuthenticatedToken(false); // no token required (0-legged)
cd.getAuthorities().add(new SimpleGrantedAuthority("ROLE_OAUTH")); // add the ROLE_OAUTH (can add others as well)
log.info("OAuth check SUCCESS, consumer key: " + consumerKey);
} else {
// deny - failed to match
throw new OAuthException("For this example, key must be 'key'");
}
return cd;
}
}
This last part is important to define the actual user (and Principal) based on the data coming in from the OAuth request. This is going to vary depending on how you handle things locally but this is an example of how to do it.
@Component
public class MyOAuthAuthenticationHandler implements OAuthAuthenticationHandler {
final static Logger log = LoggerFactory.getLogger(MyOAuthAuthenticationHandler.class);
static SimpleGrantedAuthority userGA = new SimpleGrantedAuthority("ROLE_USER");
static SimpleGrantedAuthority adminGA = new SimpleGrantedAuthority("ROLE_ADMIN");
@Override
public Authentication createAuthentication(HttpServletRequest request, ConsumerAuthentication authentication, OAuthAccessProviderToken authToken) {
Collection<GrantedAuthority> authorities = new HashSet<>(authentication.getAuthorities());
// attempt to create a user Authority
String username = request.getParameter("username");
if (StringUtils.isBlank(username)) {
username = authentication.getName();
}
// NOTE: you should replace this block with your real rules for determining OAUTH ADMIN roles
if (username.equals("admin")) {
authorities.add(userGA);
authorities.add(adminGA);
} else {
authorities.add(userGA);
}
Principal principal = new NamedOAuthPrincipal(username, authorities,
authentication.getConsumerCredentials().getConsumerKey(),
authentication.getConsumerCredentials().getSignature(),
authentication.getConsumerCredentials().getSignatureMethod(),
authentication.getConsumerCredentials().getSignatureBaseString(),
authentication.getConsumerCredentials().getToken()
);
Authentication auth = new UsernamePasswordAuthenticationToken(principal, null, authorities);
return auth;
}
public static class NamedOAuthPrincipal extends ConsumerCredentials implements Principal {
public String name;
public Collection<GrantedAuthority> authorities;
public NamedOAuthPrincipal(String name, Collection<GrantedAuthority> authorities, String consumerKey, String signature, String signatureMethod, String signatureBaseString, String token) {
super(consumerKey, signature, signatureMethod, signatureBaseString, token);
this.name = name;
this.authorities = authorities;
}
@Override
public String getName() {
return name;
}
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
}
}
@Controller
@RequestMapping("/oauth")
public class OAuthController extends BaseController {
@RequestMapping({"", "/"})
public String home(HttpServletRequest req, Principal principal, Model model) {
return "home"; // name of the template
}
}
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- security and oauth -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- OAuth -->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth</artifactId>
<version>2.0.2.RELEASE</version>
</dependency>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With