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
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>