I am experimenting with Spring Boot Security OAuth2. I would like to make some paths publicly accessible; that is, not requiring authentication. However, I am receiving the following error:
{"error":"unauthorized","error_description":"Full authentication is required to access this resource"}
PublicController
#RequestMapping("/public")
public class PublicController {
#RequestMapping(method= RequestMethod.GET)
public String test() {
return "Hello World!";
}
}
Now, normally this would be a good thing (since security is the goal), but, I have explicitly told the application to permitAll() on that path.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.password.PasswordEncoder;
import com.midamcorp.resource_server.service.OAuthUserDetailsService;
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private OAuthUserDetailsService userService;
#Autowired
private PasswordEncoder passwordEncoder;
#Bean
#Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
// Hash password
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService)
.passwordEncoder(passwordEncoder);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().antMatchers("/public/**")
.permitAll()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.httpBasic()
.realmName("test")
.and()
.csrf()
.disable();
}
}
I have tried various combinations, including using antMatches(/**) and removing the portions after the and(). However, I still receive the same error.
Thanks.
EDIT:
ResourceServerConfig
#EnableResourceServer
#Configuration
public class ResourceServerConifig extends ResourceServerConfigurerAdapter {
#Autowired
private TokenStore tokenStore;
private final AppConfig appConfig;
private AuthenticationManager authenticationManager;
#Autowired
public ResourceServerConifig(AuthenticationManager authenticationManager, AppConfig appConfig) {
this.authenticationManager = authenticationManager;
this.appConfig = appConfig;
}
#Bean
#Primary //Making this primary to avoid any accidental duplication with another token service instance of the same name
ResourceServerTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore);
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenServices(tokenServices());
}
}
ApplicationConfig
#Configuration
#EnableResourceServer
public class AppConfig {
#Bean
public TokenStore tokenStore() {
// return new JwtTokenStore(accessTokenConverter());
return new JwtTokenStore((jwtTokenEnhancer()));
}
#Bean
protected JwtAccessTokenConverter jwtTokenEnhancer() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
Resource resource = new ClassPathResource("public.cert");
String publicKey = null;
try {
publicKey = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
} catch (IOException e) {
throw new RuntimeException(e);
}
converter.setVerifierKey(publicKey);
return converter;
}
}
In your application.yml you could add this:
security:
ignored: /public,/public/**
Related
I use overridden values for spring security username and password. Following properties are in my application.properties.
spring.security.user.name=myUser
spring.security.user.password=myPassword
spring.security.user.roles=admin
I would like to encrypt the password value as follows:
spring.security.user.name=myUser
spring.security.user.password={bcrypt}hashedpassword valuevalue
spring.security.user.roles=admin
I added PasswordEncoder in my SpringConfig:
#Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
In some example I noticed that there is for AuthenitcationManagerBuilder but I do not know what datasource should be used. What else do I need to use encrypted password for default user?
#Autowired
public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
Adding my Spring Security config as a reference:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/v1/custom").hasRole("admin")
.anyRequest().authenticated()
.and()
.httpBasic();
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
Grabbing:
$2a$12$7Bg57uTtN7GWIdiqRW4h5e/aOUFagHwkEGv9byUr0bb/QbUU8S4rS
for input:
hello
... from (e.g. thx): https://bcrypt-generator.com/
With:
spring-starter (web, security),
application.properties:
spring.security.user.password={bcrypt}$2a$12$7Bg57uTtN7GWIdiqRW4h5e/aOUFagHwkEGv9byUr0bb/QbUU8S4rS
and any (rest)controller:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
#SpringBootApplication
#RestController
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
// #Bean
// public PasswordEncoder encoder() {
// return new BCryptPasswordEncoder();
// }
#GetMapping("secured")
public String secured() {
return "Hello Admin";
}
}
and without additional config,
we can login with user=user and password=hello.
Adding:
#Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
breaks this behavior!! To "fix it", we can (application.properties):
spring.security.user.password=$2a$12$7Bg57uTtN7GWIdiqRW4h5e/aOUFagHwkEGv9byUr0bb/QbUU8S4rS
.. omit the "tag" {bcrypt}! ...
Responsible for "tags"/capable of several algorithms is the DelegatingPasswordEncoder, which is auto configured (via [AuthenticationConfiguration|HttpSecurityConfiguration].LazyPasswordEncoder ..if no other PasswordEncoder bean found) and uses
PasswordEncoderFactories behind the scenes, like (currently) this:
#SuppressWarnings("deprecation")
public static PasswordEncoder createDelegatingPasswordEncoder() {
String encodingId = "bcrypt";
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put(encodingId, new BCryptPasswordEncoder());
encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder());
encoders.put("MD4", new org.springframework.security.crypto.password.Md4PasswordEncoder());
encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));
encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_5());
encoders.put("pbkdf2#SpringSecurity_v5_8", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());
encoders.put("scrypt", SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1());
encoders.put("scrypt#SpringSecurity_v5_8", SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8());
encoders.put("SHA-1", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1"));
encoders.put("SHA-256",
new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256"));
encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder());
encoders.put("argon2", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2());
encoders.put("argon2#SpringSecurity_v5_8", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8());
return new DelegatingPasswordEncoder(encodingId, encoders);
}
EDIT:
No, also introducing:
#Configuration
class WebSecurityConfig {
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeHttpRequests().requestMatchers("/secured").hasRole("admin")
.anyRequest()
.authenticated()
.and()
.httpBasic();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
return http.build();
}
}
}
Plus:
spring.security.user.roles=admin
Doesn't change my observations:
When "custom password encoder"
and $2a$12$7Bg57uTtN7GWIdiqRW4h5e/aOUFagHwkEGv9byUr0bb/QbUU8S4rS
can login with user:hello (and view /secured response)
with default/-out password encoder
and {bcrypt}$2a$12$7Bg57uTtN7GWIdiqRW4h5e/aOUFagHwkEGv9byUr0bb/QbUU8S4rS
can login with user:hello (and view /secured response)
(it only switches from form to basic login:)
Test (good/app config):
package com.example.demo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
#WebMvcTest
class WithAppConfigTest {
#Autowired
MockMvc mockMvc;
#Test
void expectAccess() throws Exception {
mockMvc.perform(
get("/secured")
.with(httpBasic("user", "hello"))
).andExpectAll(
authenticated(),
status().isOk(),
content().string("Hello Admin"));
}
#Test
void expectUnauthorized() throws Exception {
mockMvc.perform(
get("/secured")
.with(httpBasic("user", "wrong"))
).andExpectAll(
unauthenticated(),
status().isUnauthorized(),
content().bytes(new byte[0]));
}
}
Test forbidden:
#WebMvcTest(properties = "spring.security.user.roles=foo,bar,baz")
class WithFakeConfigTest {
#Autowired
MockMvc mockMvc;
#Test
void expectForbidden() throws Exception {
mockMvc.perform(
get("/secured")
.with(httpBasic("user", "hello"))
).andExpectAll(
authenticated(), // !, but:
status().isForbidden(),
content().bytes(new byte[0]));
}
}
I have a project using spring boot, spring security with oauth2. When I use
SecurityContextHolder.getContext().getAuthentication().getPrincipal()
this method returns only username end in the examples that I see this method returns UserDetails implentation.
Follow the settings
OAuthSecurityConfig.java:
package br.com.altha.api.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import br.com.altha.api.security.CustomUserDetailsService;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled=true)
#EnableAuthorizationServer
#EnableResourceServer
#Order(SecurityProperties.BASIC_AUTH_ORDER-2)
#Import(Encoders.class)
public class OAuthSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private PasswordEncoder userPasswordEncoder;
#Autowired
private CustomUserDetailsService userDetailsService;
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(userPasswordEncoder);
}
}
AuthorizationServerConfig.java:
package br.com.altha.api.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import br.com.altha.api.security.CustomUserDetailsService;
#Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private static final String SECRET = "PASSWORD";
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private PasswordEncoder oauthClientPasswordEncoder;
#Autowired
private CustomUserDetailsService userDetailsService;
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()").passwordEncoder(oauthClientPasswordEncoder);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("altha-adms")
.secret(oauthClientPasswordEncoder.encode(SECRET))
.scopes("write", "read")
.authorizedGrantTypes("password", "refresh_token")
.accessTokenValiditySeconds(60/*1800*/)
.refreshTokenValiditySeconds(1800);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter())
.authenticationManager(authenticationManager)
.reuseRefreshTokens(false)
.userDetailsService(userDetailsService);
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(SECRET);
return converter;
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
}
ResourceServerConfig.java:
package br.com.altha.api.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler;
import org.springframework.security.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler;
import br.com.altha.api.handler.RestExceptionHandler;
#Configuration
#Import(Encoders.class)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Bean
public RestExceptionHandler handlerError() {
return new RestExceptionHandler();
}
#Bean
public MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/public/**").permitAll()
.antMatchers("/private/**").authenticated()
.and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.stateless(true);
}
}
I was able to solve this problem with this code:
I added a bean to UserAuthenticationConverter
#Bean
public UserAuthenticationConverter userAuthenticationConverter() {
DefaultUserAuthenticationConverter defaultUserAuthenticationConverter = new DefaultUserAuthenticationConverter();
defaultUserAuthenticationConverter.setUserDetailsService(userDetailsService);
return defaultUserAuthenticationConverter;
}
After this, I set this bean in the JwtAccessTokenConverter
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
final JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setSigningKey(SECRET);
((DefaultAccessTokenConverter) jwtAccessTokenConverter.getAccessTokenConverter())
.setUserTokenConverter(userAuthenticationConverter());
return jwtAccessTokenConverter;
}
The cause would vary based on authentication/authorization technique but in my case I have 2 filters authentication/authorization the issue was passing the username instead of the whole user object to the UsernamePasswordAuthenticationToken in the authorization filter :
return new UsernamePasswordAuthenticationToken(userDetails.getUsername(), .....);
The first constructor argument is being set to Principle object.
So fix was to pass the whole userDetails object
return new UsernamePasswordAuthenticationToken(userDetails, .....);
With Spring Boot 1.5.x, you can implement a PrincipalExtractor and override it's Object extractPrincipal(Map<String, Object> map). The following example component has a UserdetailsService autowired in to lookup the UserDetails object based on the username.
#Component
public class MyPrincipalExtractor implements PrincipalExtractor {
private UserDetailsService userDetailsService;
#Value("${security.oauth2.client.principal-attribute}")
private String principalAttribute;
#Autowired
public MyPrincipalExtractor(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
#Override
public Object extractPrincipal(Map<String, Object> map) {
if (!map.containsKey(principalAttribute)) {
return null;
}
final String username = (String) map.get(principalAttribute);
try {
return userDetailsService.loadUserByUsername(username);
} catch (UsernameNotFoundException e) {
// This may be the first time this user is accessing the system,
// maybe you want to extract some other attributes from the map
// and return a different type of user object that can be used to
// create a new user.
}
}
}
Now the SecurityContextHolder.getContext().getAuthentication().getPrincipal() will contain a UserDetails object.
For a more detailed tutorial see:
https://www.baeldung.com/spring-security-oauth-principal-authorities-extractor
https://docs.spring.io/spring-security-oauth2-boot/docs/current-SNAPSHOT/api/org/springframework/boot/autoconfigure/security/oauth2/resource/PrincipalExtractor.html
I want to make an authentication with password encoding.
When I use PasswordEncoder() and setPasswordEncryptor(), it gives me errors:
PasswordEncoder() : Cannot instantiate the type PasswordEncoder and for setPasswordEncryptor() : The method setPasswordEncryptor(StrongPasswordEncryptor) is undefined for the type PasswordEncoder.
I don't know what I should I do and I search for it too much.
thank you guys!
import org.jasypt.util.password.StrongPasswordEncryptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private AuthenticationProvider authenticationProvider;
#Autowired
#Qualifier("daoAuthenticationProvider")
public void setAuthenticationProvider(AuthenticationProvider authenticationProvider) {
this.authenticationProvider = authenticationProvider;
}
#Bean
public PasswordEncoder passwordEncoder(StrongPasswordEncryptor passwordEncryptor) {
PasswordEncoder passwordEncoder = new PasswordEncoder();
passwordEncoder.setPasswordEncryptor(passwordEncryptor);
return passwordEncoder;
}
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider(PasswordEncoder passwordEncoder,
UserDetailsService userDetailsService) {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
return daoAuthenticationProvider;
}
#Autowired
public void configureAuthManager(AuthenticationManagerBuilder authenticationManagerBuilder) {
authenticationManagerBuilder.authenticationProvider(authenticationProvider);
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.authorizeRequests().antMatchers("/", "/products", "/product/show/*", "/console/*", "/h2-console/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login").permitAll()
.and()
.logout().permitAll();
httpSecurity.csrf().disable();
httpSecurity.headers().frameOptions().disable();
}
}
Try the following code snippet. It should work.
#Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder(11);
}
#Bean
public DaoAuthenticationProvider authProvider(UserDetailsService userDetailsService) {
final DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(encoder());
return authProvider;
}
Use this : Spring Boot 2
#Bean(name = "passwordEncoder")
#Qualifier("passwordEncoder")
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
I genereted an application using JHipster and I would like to use my instance of jasig CAS as SSO with my application instead of default form login that comes with the app. Ultimately I would like to use custom CAS parameters to assign authorities.
I followed this example. Now I have the app generated with JHipster which I successfully connected to my CAS. When I type localhost:8080/app/login it redirects me to my CAS instance, authenticates me and redirects back to JHipster app but JHipster doesn't let me browse the secured part of my the app and still requires authentication. Below is my SecurityConfiguration.java. Can somone provide any insight as to how proceed from here?
package com.my.company.application.config;
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.jasig.cas.client.validation.Cas20ServiceTicketValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken;
import org.springframework.security.cas.web.CasAuthenticationFilter;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import pl.edu.uw.dsk.konferator.security.*;
import pl.edu.uw.dsk.konferator.web.filter.CsrfCookieGeneratorFilter;
import javax.inject.Inject;
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private static final String CAS_URL_LOGIN = "cas.url.login";
private static final String CAS_URL_LOGOUT = "cas.url.logout";
private static final String CAS_URL_PREFIX = "cas.url.prefix";
private static final String CAS_SERVICE_URL = "app.service.security";
private static final String CAS_CALLBACK = "/auth/cas";
#Inject
private Environment env;
#Inject
private AjaxLogoutSuccessHandler ajaxLogoutSuccessHandler;
/*#Inject
private AjaxAuthenticationSuccessHandler ajaxAuthenticationSuccessHandler;*/
#Inject
private AjaxAuthenticationFailureHandler ajaxAuthenticationFailureHandler;
#Inject
private AuthenticationUserDetailsService<CasAssertionAuthenticationToken> userDetailsService;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public ServiceProperties serviceProperties() {
ServiceProperties sp = new ServiceProperties();
sp.setService(env.getRequiredProperty(CAS_SERVICE_URL));
sp.setSendRenew(false);
return sp;
}
#Bean
public SimpleUrlAuthenticationSuccessHandler authenticationSuccessHandler() {
SimpleUrlAuthenticationSuccessHandler authenticationSuccessHandler = new SimpleUrlAuthenticationSuccessHandler();
authenticationSuccessHandler.setDefaultTargetUrl("/");
authenticationSuccessHandler.setTargetUrlParameter("spring-security-redirect");
return authenticationSuccessHandler;
}
#Bean
public RememberCasAuthenticationProvider casAuthenticationProvider() {
RememberCasAuthenticationProvider casAuthenticationProvider = new RememberCasAuthenticationProvider();
casAuthenticationProvider.setAuthenticationUserDetailsService(userDetailsService);
casAuthenticationProvider.setServiceProperties(serviceProperties());
casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator());
casAuthenticationProvider.setKey("CAS_AUTHENTICATION_PROVIDER");
return casAuthenticationProvider;
}
#Bean
public SessionAuthenticationStrategy sessionStrategy() {
SessionFixationProtectionStrategy sessionStrategy = new SessionFixationProtectionStrategy();
sessionStrategy.setMigrateSessionAttributes(false);
//sessionStrategy.setRetainedAttributes(Arrays.asList("CALLBACKURL"));
return sessionStrategy;
}
/*
#Bean
public Saml11TicketValidator casSamlServiceTicketValidator() {
return new Saml11TicketValidator(env.getRequiredProperty(CAS_URL_PREFIX));
}
*/
#Bean
public Cas20ServiceTicketValidator cas20ServiceTicketValidator() {
return new Cas20ServiceTicketValidator(env.getRequiredProperty(CAS_URL_PREFIX));
}
#Bean
public CasAuthenticationFilter casAuthenticationFilter() throws Exception {
CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter();
casAuthenticationFilter.setAuthenticationManager(authenticationManager());
casAuthenticationFilter.setAuthenticationDetailsSource(new RememberWebAuthenticationDetailsSource());
casAuthenticationFilter.setSessionAuthenticationStrategy(sessionStrategy());
casAuthenticationFilter.setAuthenticationFailureHandler(ajaxAuthenticationFailureHandler);
casAuthenticationFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler());
casAuthenticationFilter.setFilterProcessesUrl(CAS_CALLBACK);
// casAuthenticationFilter.setRequiresAuthenticationRequestMatcher(new
// AntPathRequestMatcher("/login", "GET"));
return casAuthenticationFilter;
}
#Bean
public RememberCasAuthenticationEntryPoint casAuthenticationEntryPoint() {
RememberCasAuthenticationEntryPoint casAuthenticationEntryPoint = new RememberCasAuthenticationEntryPoint();
casAuthenticationEntryPoint.setLoginUrl(env.getRequiredProperty(CAS_URL_LOGIN));
casAuthenticationEntryPoint.setServiceProperties(serviceProperties());
//move to /app/login due to cachebuster instead of api/authenticate
casAuthenticationEntryPoint.setPathLogin("/app/login");
return casAuthenticationEntryPoint;
}
#Bean
public SingleSignOutFilter singleSignOutFilter() {
SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
singleSignOutFilter.setCasServerUrlPrefix(env.getRequiredProperty(CAS_URL_PREFIX));
return singleSignOutFilter;
}
#Bean
public LogoutFilter requestCasGlobalLogoutFilter() {
LogoutFilter logoutFilter = new LogoutFilter(env.getRequiredProperty(CAS_URL_LOGOUT) + "?service="
+ env.getRequiredProperty(CAS_SERVICE_URL), new SecurityContextLogoutHandler());
// logoutFilter.setFilterProcessesUrl("/logout");
// logoutFilter.setFilterProcessesUrl("/j_spring_cas_security_logout");
logoutFilter.setLogoutRequestMatcher(new AntPathRequestMatcher("/api/logout", "POST"));
return logoutFilter;
}
#Inject
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(casAuthenticationProvider());
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/scripts/**/*.{js,html}")
.antMatchers("/bower_components/**")
.antMatchers("/i18n/**")
.antMatchers("/assets/**")
.antMatchers("/swagger-ui/index.html")
.antMatchers("/test/**")
.antMatchers("/h2-console/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterAfter(new CsrfCookieGeneratorFilter(), CsrfFilter.class)
.addFilterBefore(casAuthenticationFilter(), BasicAuthenticationFilter.class)
.addFilterBefore(singleSignOutFilter(), CasAuthenticationFilter.class)
.addFilterBefore(requestCasGlobalLogoutFilter(), LogoutFilter.class)
.exceptionHandling()
.authenticationEntryPoint(casAuthenticationEntryPoint())
// .and()
// .rememberMe()
// .rememberMeServices(rememberMeServices)
// .key(env.getProperty("jhipster.security.rememberme.key"))
// .and()
// .formLogin()
// .loginProcessingUrl("/api/authentication")
// .successHandler(ajaxAuthenticationSuccessHandler)
// .failureHandler(ajaxAuthenticationFailureHandler)
// .usernameParameter("j_username")
// .passwordParameter("j_password")
// .permitAll()
.and()
.logout()
.logoutUrl("/api/logout")
.logoutSuccessHandler(ajaxLogoutSuccessHandler)
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.permitAll()
.and()
.headers()
.frameOptions()
.disable()
.and()
.authorizeRequests()
.antMatchers("/app/**").authenticated()
.antMatchers("/api/register").permitAll()
.antMatchers("/api/activate").permitAll()
.antMatchers("/api/authenticate").permitAll()
.antMatchers("/api/account/reset_password/init").permitAll()
.antMatchers("/api/account/reset_password/finish").permitAll()
.antMatchers("/api/logs/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/api/audits/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/api/**").authenticated()
.antMatchers("/metrics/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/health/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/trace/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/dump/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/shutdown/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/beans/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/configprops/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/info/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/autoconfig/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/env/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/trace/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/mappings/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/liquibase/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/v2/api-docs/**").permitAll()
.antMatchers("/configuration/security").permitAll()
.antMatchers("/configuration/ui").permitAll()
.antMatchers("/swagger-ui/index.html").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/protected/**").authenticated();
}
#EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
private static class GlobalSecurityConfiguration extends GlobalMethodSecurityConfiguration {
public GlobalSecurityConfiguration() {
super();
}
}
}
Jhipste doesn't know that you are authenticated, thats the reason it does not let you browse secure pages. If you look closely in your principal.service.js and auth.service.js, they are calling the api/account rest service in AccountResource, and based on this angular determins if you are authenticated or not. So that means we need to return an User from this rest service.
Once you logged in with cas the current user info can be obtained from the AccounResource Rest service. Below is my AccountResource
/**
* GET /account -> get the current user.
*/
#RequestMapping(value = "/account",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#Timed
public ResponseEntity<UserDTO> getAccount() {
UserDetails userDetails = (UserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
List<String> roles = new ArrayList<>();
for (GrantedAuthority authority : userDetails.getGrantedAuthorities()) {
roles.add(authority.getAuthorigy());
}
return new ResponseEntity<>(
new UserDTO(
user.getLogin(),
null,
null,
null,
null,
null,
roles),
HttpStatus.OK);
}
As you can see above, i am getting authentication(casauthentication) token from security context. I have refactored my AccountResource to remove all the reference to user repository as i am no longer using the database to store user info.
Hope this helps.
I am using Spring Security with Spring Boot and authenticate my users via JASIG CAS. Some of the pages require explicitly authentication (.authenticated()) and some of them are for all users.
Now there is a certain area in the menu, which indicates the current user and possible actions, like login/logout.
My main problem is now that the main page is public (permitAll()) and that, if a user already has a CAS session through some other application, he is shown as "anonymousUser" until login is clicked manually or a protected page is opened.
Is there somebody who has any ideas on how to get this working?
My security configuration:
import org.jasig.cas.client.validation.Cas20ServiceTicketValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
import org.springframework.security.cas.web.CasAuthenticationFilter;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private Environment env;
#Autowired
private CasAuthenticationProvider authProvider;
#Bean
public ServiceProperties serviceProperties() {
ServiceProperties sp = new ServiceProperties();
sp.setSendRenew(false);
sp.setService(env.getProperty("app.url") + "/j_spring_cas_security_check");
return sp;
}
#SuppressWarnings("rawtypes")
#Autowired
private AuthenticationUserDetailsService customUserDetailsService() {
return new CASUserDetailsService();
}
#Bean
public CasAuthenticationProvider casAuthenticationProvider() {
CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider();
casAuthenticationProvider.setAuthenticationUserDetailsService(customUserDetailsService());
casAuthenticationProvider.setServiceProperties(serviceProperties());
casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator());
casAuthenticationProvider.setKey("an_id_for_this_auth_provider_only");
return casAuthenticationProvider;
}
#Bean
public Cas20ServiceTicketValidator cas20ServiceTicketValidator() {
return new Cas20ServiceTicketValidator(env.getProperty("cas.service.url"));
}
#Bean
public CasAuthenticationFilter casAuthenticationFilter() throws Exception {
CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter();
casAuthenticationFilter.setAuthenticationManager(authenticationManager());
casAuthenticationFilter.setAuthenticationSuccessHandler(savedRequestAwareAuthenticationSuccessHandler());
return casAuthenticationFilter;
}
#Bean
public CasAuthenticationEntryPoint casAuthenticationEntryPoint() {
CasAuthenticationEntryPoint ep = new CasAuthenticationEntryPoint();
ep.setLoginUrl(env.getProperty("cas.service.url") + "/login");
ep.setServiceProperties(serviceProperties());
return ep;
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/js/**").antMatchers("/fonts/**").antMatchers("/images/**").antMatchers("/css/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling().
authenticationEntryPoint(casAuthenticationEntryPoint()).and().addFilter(casAuthenticationFilter()).
logout().logoutUrl("/caslogout").addLogoutHandler(logoutHandler()).logoutSuccessUrl("/").deleteCookies("JSESSIONID").permitAll().and().
csrf().disable().headers().frameOptions().disable().authorizeRequests().antMatchers("/rest/**").permitAll().
antMatchers("/login/**").authenticated().antMatchers("/settings/**").authenticated().
antMatchers("/projects/*/settings").authenticated().antMatchers("/projects/*/role").authenticated().
antMatchers("/projects/*/*/admin").authenticated().antMatchers("/**").permitAll();
}
#Bean
public SavedRequestAwareAuthenticationSuccessHandler savedRequestAwareAuthenticationSuccessHandler() {
CASAuthSuccessHandler auth = new CASAuthSuccessHandler();
return auth;
}
#Bean
public CASLogoutHandler logoutHandler() {
CASLogoutHandler logout = new CASLogoutHandler();
return logout;
}
#EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
private static class GlobalSecurityConfiguration extends GlobalMethodSecurityConfiguration {
}
}
What you are looking for is CAS's Gateway feature. Currently this is not supported in Spring Security. There is a JIRA logged to support it and a Pull Request that is waiting additional modifications based on my feedback to the submitter.
I'd take a look at the Pull Request as it demonstrates a few options on how to implement this. Please do read through the whole thing as you will need to make some changes to the Pull Request to ensure your application performs.