I want to allow access for unauthenticated only to a few paths: /everyone1/something1, /everyone2/something2 and /everyone3/**.
For the rest of the paths, I want only authenticated requests to be allowed.
For now, I have "class WebSecurityConfig extends WebSecurityConfigurerAdapter" with:
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(
jwtUtils, this.accessCookie, this.selectedRoleScopeCookie);
httpSecurity.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
httpSecurity.cors().and().csrf().disable();
httpSecurity.authorizeRequests()
.antMatchers("/everyone1/something1", "/everyone2/something2", "/everyone3/**")
.permitAll()
.anyRequest().authenticated()
.and().httpBasic().disable();
}
and in "jwtAuthenticationFilter" I set authentication as:
private void setAuthentication2(String username, String someData, boolean authenticated) {
User user = new User(username, "", new ArrayList<>());
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
if (!authenticated) {
authentication.setAuthenticated(false);
}
AuthenticationDetails authenticationDetails = new AuthenticationDetails(someData);
authentication.setDetails(authenticationDetails);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
Unfortunately, the above configuration blocks every request, both authenticated and unauthenticated.
any help would be appreciated.
Thanks!
This method authorizes some paths for authenticated requests. What you need is:
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/everyone1/something1", "/everyone2/something2", "/everyone3/**");
}
Then anonymous requests can access this path.
Related
I am wondering if there is a way to provide two separate types of authentication?
User should log, register, get user data for endpoints /login, /register, /user using basic auth. And when I call /api it should only be authenticated with JWT token provided in headers.
But when I call /api I get all data without any authentication. When user is logged and call /user, API gives JWT to access /api.
My code:
Configuration for basic auth:
#Configuration
#EnableWebSecurity
#Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf().disable();
http
.authorizeRequests()
.antMatchers("/user").authenticated()
.antMatchers("/register").permitAll()
.and()
.formLogin().permitAll()
.defaultSuccessUrl("/user");
}
Configuration for JWT auth:
#Configuration
#Order(2)
public class JWTSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.antMatcher("/api/**")
.addFilterAfter(new JWTAuthorizationFilter(),UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic().disable();
}
I had the same problem, I wanted basic Authentication for some endpoints and for some other I wanted other authentication methods. like yours. you wanna basic authentication for some of the endpoints (/login,/register, /user ) and JWT authentication for some other(/api/**).
I used some tutorials about multiple entry points in spring security but it didn't work.
So here is my solution (It worked)
Separate basic authentication from JWT authentication by creating a custom filter.
Add a prefix path for the endpoints that should be authenticated using basic authentication. like :
(/basic/login, /basic/register,/basic/user)
Create a new custom filter for /basic prefix (for /basic requests) and check basic authentication
#Component
public class BasicAuthenticationFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
//Check for the requests that starts with /basic
if (httpServletRequest.getRequestURI().startsWith("/basic/")) {
try {
//Fetch Credential from authorization header
String authorization = httpServletRequest.getHeader("Authorization");
String base64Credentials = authorization.substring("Basic".length()).trim();
byte[] credDecoded = Base64.getDecoder().decode(base64Credentials);
String credentials = new String(credDecoded, StandardCharsets.UTF_8);
final String username = credentials.split(":", 2)[0];
final String password = credentials.split(":", 2)[1];
//Check the username and password
if (username.equals("admin") && password.equals("admin")) {
//continue
chain.doFilter(request, response);
} else
throw new AuthenticationCredentialsNotFoundException("");
} catch (Exception e) {
throw new AuthenticationCredentialsNotFoundException("");
}
} else chain.doFilter(request, response);
}
}
Write main security configuration just for JWT and permit /basic URL
#Configuration
#EnableWebSecurity
public class JWTSecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/basic/**").permitAll().and()
.csrf().disable()
.antMatcher("/api/**")
.addFilterAfter(new JWTAuthorizationFilter(),UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic().disable();
}
I am trying to toggle/bypass/disable Spring Security (Authentication and Authorization) for all the requests having particular Request Header.
For example, if a request url is hit with that Request Header, Spring Security should be bypassed, if not it should not be bypassed.
For this, I am using following requestMatchers Spring Security config:
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.GET)
.antMatchers(HttpMethod.OPTIONS)
.requestMatchers(new RequestHeaderRequestMatcher("TEST-HEADER","TEST-VALUE"));
}
My remaining Security Config is :
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity (prePostEnabled = true)
#ConditionalOnProperty (name = "security.enabled", havingValue = "true", matchIfMissing = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private SecurityProps securityProps;
#Autowired
private MyUserDetailsService myUserDetailsService;
#Autowired
private MyAuthenticationEntryPoint myAuthenticationEntryPoint;
#Autowired
private MyCORSFilter myCORSFilter;
public SecurityConfig() {
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.addFilterBefore(myCORSFilter, SessionManagementFilter.class)
.addFilterBefore(requestHeaderFilter(), RequestHeaderAuthenticationFilter.class)
.authenticationProvider(preauthAuthProvider())
.authorizeRequests()
.antMatchers(HttpMethod.GET, securityProps.getNoAuthGetPattern()).permitAll()
.antMatchers(HttpMethod.OPTIONS, securityProps.getNoAuthOptionsPattern()).permitAll()
.requestMatchers(new RequestHeaderRequestMatcher("TEST-HEADER","TEST-VALUE")).permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(myAuthenticationEntryPoint);
}
#Autowired
#Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(preauthAuthProvider());
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.GET)
.antMatchers(HttpMethod.OPTIONS)
.requestMatchers(new RequestHeaderRequestMatcher("TEST-HEADER","TEST-VALUE"));
}
public RequestHeaderAuthenticationFilter requestHeaderFilter() throws Exception {
RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter = new RequestHeaderAuthenticationFilter();
requestHeaderAuthenticationFilter.setPrincipalRequestHeader(MySecurityConstants.LOGIN_HEADER);
requestHeaderAuthenticationFilter.setAuthenticationManager(authenticationManager());
requestHeaderAuthenticationFilter.setExceptionIfHeaderMissing(false);
requestHeaderAuthenticationFilter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
if (exception instanceof MySecurityException) {
myAuthenticationEntryPoint.commenceMySecurityException(request, response, (MySecurityException) exception);
} else if (exception instanceof UsernameNotFoundException) {
myAuthenticationEntryPoint.commenceUsernameNotFoundException(request, response,
(UsernameNotFoundException) exception);
} else if (exception instanceof PreAuthenticatedCredentialsNotFoundException) {
myAuthenticationEntryPoint.commence(request, response, exception);
}
}
});
return requestHeaderAuthenticationFilter;
}
#Bean
public PreAuthenticatedAuthenticationProvider preauthAuthProvider() throws Exception {
PreAuthenticatedAuthenticationProvider authProvider = new PreAuthenticatedAuthenticationProvider();
authProvider.setPreAuthenticatedUserDetailsService(userDetailsServiceWrapper());
return authProvider;
}
#Bean
public UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> userDetailsServiceWrapper()
throws Exception {
UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> wrapper =
new UserDetailsByNameServiceWrapper<>();
wrapper.setUserDetailsService(ivyUserDetailsService);
return wrapper;
}
}
With the above settings, I am unable to disable/bypass Spring Security and I am getting the AuthenticationCredentialsNotFoundException exception:
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
Can anyone help me by identifying what am I doing wrong? Is my approach correct or I need to do something else to achieve this?
EDIT :
I am getting this exception in org.springframework.security.access.intercept.AbstractSecurityInterceptor class in beforeInvocation() method where it tries to get the authentication object from SecurityContextHolder. AbstractSecurityInterceptor is invoked by its subclass MethodSecurityInterceptor which is invoked from my Spring Controller which is annotated with #PreAuthorize.
I think your bypass is working fine. Its skipping the check.
The security's authorization check part gets the authenticated object from SecurityContext, which will be set when a request gets through the spring security filter.
So when you skip security filter SecurityContext is not set yet thus the error
You can do something like this to set it manually for your Custom Header Case
try {
SecurityContext ctx = SecurityContextHolder.createEmptyContext();
SecurityContextHolder.setContext(ctx);
ctx.setAuthentication(event.getAuthentication());
} finally {
SecurityContextHolder.clearContext();
}
Edit 1:
Answering all the queries.
But if thats the case, then I guess all GET call should also have
failed, but my GET calls are working fine.
Since you have added this line All your GET calls are skipped from security check.
.antMatchers(HttpMethod.GET, securityProps.getNoAuthGetPattern()).permitAll()
where can I add the code you have mentioned? Any particular filter or
somewhere else ?
I have done something like this in a Filter.
Refer Here
Look at TokenAuthenticationFilter Class in Answer. Where am manually setting.
Note: Its JWT implementation but good to refer
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (tokenHelper.validateToken(authToken, userDetails)) {
// create authentication
TokenBasedAuthentication authentication = new TokenBasedAuthentication(userDetails);
authentication.setToken(authToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
What is event in your answer?
I just got that case from Some Answer, cant find its link now. But you can setAuthentication like this or like above
Authentication authentication = new PreAuthenticatedAuthenticationToken("system", null);
authentication.setAuthenticated(true);
context.setAuthentication(authentication);
I've my custom implementation of Spring security in a Spring boot application. So I have my dependencies and I've a class named SecurityImpl which implements for me the login access.
When I get on the browser I'm correctly asked to login with an alert. When I login I have access to all of the #RequestMapping of my Spring Controller correctly. But I remain always logged. Even if I delete the JSESSIONID from my browser, when I make another http request, I am allowed and a new JSESSIONID is created and sent to my browser.
One weird thing is that even when I access with the login for the first time, even when the cookie is authomatically generated, the expiration date is: 1969-12-31T23:59:59.000Z
I've tried to invalidate the session, to delete the cookies from the server, to logout in various ways but nothing. Once logged, I am always allowed.
Here my SecurityImpl.java class which configurates my Spring Security:
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
#Configuration
#Component
public class SecurityImpl extends WebSecurityConfigurerAdapter implements AuthenticationProvider {
public static final String ROLE_ADMIN = "ROLE_ADMIN";
public static final String ROLE_USER = "ROLE_USER";
#Autowired UtenteDao utenteDao;
/* authentication provider part */
#Override
public Authentication authenticate(Authentication auth) throws AuthenticationException {
String username = auth.getName();
String password = auth.getCredentials().toString();
String ruolo = "";
Optional<Utente> utenteOptional = utenteDao.findByCodiceFiscaleAndPassword(username, password);
if(utenteOptional.isPresent()){
ruolo = utenteOptional.get().getRuolo();
}
if(ROLE_ADMIN.equals(ruolo)) {
List<GrantedAuthority> grantedAuths = new ArrayList<>();
grantedAuths.add(new SimpleGrantedAuthority(ROLE_USER));
grantedAuths.add(new SimpleGrantedAuthority(ROLE_ADMIN));
return new UsernamePasswordAuthenticationToken(username, password, grantedAuths);
} else if(ROLE_USER.equals(ruolo)){
List<GrantedAuthority> grantedAuths = new ArrayList<>();
grantedAuths.add(new SimpleGrantedAuthority(ROLE_USER));
return new UsernamePasswordAuthenticationToken(username, password, grantedAuths);
} else {
throw new BadCredentialsException("Autenticazione fallita");
}
}
#Override
public boolean supports(Class<?> auth) {
return auth.equals(UsernamePasswordAuthenticationToken.class);
}
/* websecurity adapter part: erase it if you don't want login alert but default spring login web page */
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(this); //this because it is either a WebSecurityAdapter than an AuthenticationProvider
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated()
.and()
.httpBasic()
.and()
.logout().clearAuthentication(true).logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/test")
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true);
}
/* per non filtrare con il login alcuni path */
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/test");
}
}
It doesn't work: when I go to /logout I'm redirected to /test correctly but when I ask for a forbidden path I'm allowed without any login.
Then I tried some solution in my #RestController:
#RequestMapping("/logout")
public String logoutPage (UsernamePasswordAuthenticationToken token) {
token.eraseCredentials();
token.setAuthenticated(false);
SecurityContextHolder.getContext().setAuthentication(null);
return "<h1>Logout effettuato con successo.</h1>";
}
then I tried:
#RequestMapping(value = "/logout")
public String loadApp(HttpServletRequest request) {
HttpSession session= request.getSession(false);
SecurityContextHolder.clearContext();
if(session != null) {
session.invalidate();
}
return "<h1>Logout effettuato con successo.</h1>";
}
Then, as a desperate, I tried:
#RequestMapping("/logout")
public String logoutDo(HttpServletRequest request){
HttpSession session= request.getSession(false);
SecurityContextHolder.clearContext();
session= request.getSession(false);
if(session != null) {
session.invalidate();
}
for(Cookie cookie : request.getCookies()) {
cookie.setMaxAge(0);
}
return "<h1>Logout effettuato con successo.</h1>";
}
I tried to use these methods and contemporarily delete my cookie from the browser. I've also tried to preauthorize forbidden method with the annotation #PreAuthorize, in the case they would be allowed (when you open a new browser, before first login, they are NOT allowed even without #PreAuthorize, but when login is made, IS FOREVER!)
The problem was the absence of the usage of showForm(). Without it, yes I insert my credentials within a Javascript alert which is presented to me. But no logout is possible.
So the code changes this way:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.httpBasic()
.and()
.logout().clearAuthentication(true).logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/test")
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true);
}
I am creating a web app, which has two type of users, say A and B. Currently, the login flow is like this,
There is single login link on index.html, which points to /login
The user is redirected to google login page, where user logins and is redirected to index.html
At this point, I have to verify whether the user is authenticated and if yes, what is the type of user (A, B or new user), the further process is then to redirect them to appropriate links.
The type of the user is decided based upon whether an entry exists in two tables, one for A and one for B.
The code looks like this,
#EnableOAuth2Sso
#Configuration
public class WebSecurityConfigurator extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/", "/index.html")
.permitAll()
.anyRequest()
.authenticated();
}
}
I'd like to simplify this process, wherein,
There are two login links, say /login-A and /login-B
The user clicks one of them, gets redirected to Google, authenticates, and redirects to the appropriate page.
If you use #EnableOAuth2Client in place of #EnableOAuth2Sso, you would be able to define multiple OAuth2ClientAuthenticationProcessingFilter.
Your configuration would be something like ..
#EnableOAuth2Client
#RestController
#Configuration
public class WebSecurityConfigurator extends WebSecurityConfigurerAdapter {
#Autowired
OAuth2ClientContext oauth2ClientContext;
#RequestMapping("/user")
public Principal user(Principal principal) {
return principal;
}
// #formatter:off
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/", "/login**", "/webjars/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.logout()
.logoutSuccessUrl("/").permitAll().and().csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
// #formatter:on
private Filter ssoFilter() {
CompositeFilter filter = new CompositeFilter();
List filters = new ArrayList<>();
OAuth2ClientAuthenticationProcessingFilter googleFilterA = new OAuth2ClientAuthenticationProcessingFilter(
"/login/googleA");
OAuth2RestTemplate googleTemplateA = new OAuth2RestTemplate(googleA(), oauth2ClientContext);
googleFilterA.setRestTemplate(googleTemplateA);
tokenServices = new UserInfoTokenServices(googleResource().getUserInfoUri(), googleA().getClientId());
tokenServices.setRestTemplate(googleTemplateA);
googleFilterA.setTokenServices(tokenServices);
OAuth2ClientAuthenticationProcessingFilter googleFilterB = new OAuth2ClientAuthenticationProcessingFilter(
"/login/googleB");
OAuth2RestTemplate googleTemplateB = new OAuth2RestTemplate(googleB(), oauth2ClientContext);
googleFilterB.setRestTemplate(googleTemplateB);
tokenServices = new UserInfoTokenServices(googleResource().getUserInfoUri(), googleB().getClientId());
tokenServices.setRestTemplate(googleTemplateB);
googleFilterB.setTokenServices(tokenServices);
filters.add(googleFilterA);
filters.add(googleFilterB);
filter.setFilters(filters);
return filter;
}
#Bean
public FilterRegistrationBean oauth2ClientFilterRegistration(OAuth2ClientContextFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(filter);
registration.setOrder(-100);
return registration;
}
#Bean
#ConfigurationProperties("google.clientA")
public AuthorizationCodeResourceDetails googleA() {
return new AuthorizationCodeResourceDetails();
}
#Bean
#ConfigurationProperties("google.resource")
public ResourceServerProperties googleResource() {
return new ResourceServerProperties();
}
#Bean
#ConfigurationProperties("google.clientB")
public AuthorizationCodeResourceDetails googleB() {
return new AuthorizationCodeResourceDetails();
}
}
And you will define following properties in your application.yml file
google:
clientA:
clientId: 12894100090-tqso3lih5o42isneort886la2pesafmp.apps.googleusercontent.com
clientSecret: 9xfU16efvxQ-BTMsXT9wOLpw
accessTokenUri: https://accounts.google.com/o/oauth2/token
userAuthorizationUri: https://accounts.google.com/o/oauth2/auth
clientAuthenticationScheme: form
scope: profile email
redirect-uri: http://yourapp.com/pathA
clientB:
clientId: 12894100090-tqso3lih5o42isneort886la2pesafmp.apps.googleusercontent.com
clientSecret: 9xfU16efvxQ-BTMsXT9wOLpw
accessTokenUri: https://accounts.google.com/o/oauth2/token
userAuthorizationUri: https://accounts.google.com/o/oauth2/auth
clientAuthenticationScheme: form
scope: profile email drive
redirect-uri: http://yourapp.com/pathB
resource:
userInfoUri: https://www.googleapis.com/oauth2/v3/userinfo
Your index.html will have two links /login/googleA and /login/googleB for the two types of users.
Refer following tutorials for more details.
https://spring.io/guides/tutorials/spring-boot-oauth2/
http://www.littlebigextra.com/spring-boot-oauth2-tutorial-for-authorizing-through-facebook-google-linkedin-and-twitter/
Update -
For redirecting to different pages you can extend OAuth2ClientAuthenticationProcessingFilter class for the two clients like..
class GoogleAAuthenticationProcessingFilter extends OAuth2ClientAuthenticationProcessingFilter {
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, Authentication authResult) throws IOException, ServletException {
super.successfulAuthentication(request, response, chain, authResult);
// here you can redirect to whatever location you want to
}
}
class GoogleBAuthenticationProcessingFilter extends OAuth2ClientAuthenticationProcessingFilter {
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, Authentication authResult) throws IOException, ServletException {
super.successfulAuthentication(request, response, chain, authResult);
// here you can redirect to whatever location you want to
}
}
And use the extended classes
OAuth2ClientAuthenticationProcessingFilter googleFilterA = new GoogleAAuthenticationProcessingFilter(
"/login/googleA");
OAuth2ClientAuthenticationProcessingFilter googleFilterB = new GoogleBAuthenticationProcessingFilter(
"/login/googleB");
Basically it's possible, but requires more configuration effort comparing to a single #EnableOAuth2Sso annotation. When this annotation is used it applies SsoSecurityConfigurer which registers dedicated OAuth2ClientAuthenticationProcessingFilter with login page path from OAuth2SsoProperties (/login by default) for given pattern (/** in your case). However it's not possible to have multiple #EnableOAuth2Sso and OAuth2SsoProperties classes in the same application. So you need to manually register multiple WebSecurityConfigurerAdapter with corresponding OAuth2ClientAuthenticationProcessingFilter with different login pages/patterns/authorization logic for each type of users.
1 - Configure Spring Security Configuration file
2 - Write Custom Authentication Success Handler class
<user name="userA" password="userApass" authorities="ROLE_A" />
<user name="userB" password="userBpass" authorities="ROLE_B" />
protected String determineTargetUrl(Authentication authentication) {
boolean isUserA = false;
boolean isUserB= false;
Collection<? extends GrantedAuthority> authorities
= authentication.getAuthorities();
for (GrantedAuthority grantedAuthority : authorities) {
if (grantedAuthority.getAuthority().equals("ROLE_A")) {
isUserA = true;
break;
} else if (grantedAuthority.getAuthority().equals("ROLE_B")) {
isUserB = true;
break;
}
}
if (isUserA) {
return "/pageA.html";
} else if (isUserB) {
return "/pageB.html";
} else {
throw new IllegalStateException();
}
}
Description
I have a authorization Server and Client Server.
The authorization Server works well, I tested it with postman to get accessToken and authorized code.
But the Client Server doesn't work.
In the authorization_code mode, client login, then get authorized code from authorization Server successfully, the next step, browser should redirect to the redirect_uri, but it didn't, it redirected to client's login page.
Info
java8, spring-boot-starter-parent-1.4.5.RELEASE, spring-boot-starter-security, spring-security-oauth2
problem location
org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainAccessToken(OAuth2ProtectedResourceDetails, AccessTokenRequest)
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth instanceof AnonymousAuthenticationToken) {
if (!resource.isClientOnly()) {
throw new InsufficientAuthenticationException(
"Authentication is required to obtain an access token (anonymous not allowed)");
}
}
The Authentication from SecurityContextHolder is AnonymousAuthenticationToken, and I don't know why.
client Server config
#SpringBootApplication
#EnableOAuth2Client
public class App {
.............
}
#Configuration
public class CustomWebMvcConfig extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/login").setViewName("login");
super.addViewControllers(registry);
}
}
#Configuration
public class CustomWebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
super.configure(auth);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/resources/**", "/webjars/**", "/img/**").permitAll()
.antMatchers("/login").permitAll()
.antMatchers("/getCurrentUserInfo").authenticated()//the resource that need access token
.anyRequest().permitAll()
.and()
.formLogin().loginPage("/login").failureUrl("/login?error")
.defaultSuccessUrl("/")
.and()
.csrf()
.disable();
}
.............
#Autowired
private OAuth2ClientContext clientContext;
#RequestMapping("/getCurrentUserInfo")
#ResponseBody
public Map<String, String> getCurrentUserInfo(){
AuthorizationCodeResourceDetails resourceDetails = new AuthorizationCodeResourceDetails();
resourceDetails.setClientId("authorization_code");
resourceDetails.setClientSecret("123456");
resourceDetails.setAccessTokenUri("http://localhost:8080/oauth/token");
resourceDetails.setUserAuthorizationUri("http://localhost:8080/oauth/authorize");
resourceDetails.setScope(Arrays.asList("empty")); OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resourceDetails, clientContext);
Map<String, String> result = restTemplate.getForObject(URI.create("http://localhost:8082/user/getCurrentUserInfo"), HashMap.class);
logger.debug("------------------------- result: {}",result);
return result;
}
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
private static List<String> grantTypes = Arrays.asList("authorization_code", "password", "client_credentials", "implicit");
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if(!grantTypes.contains(username)){
throw new UsernameNotFoundException(String.format("用户 %s 不存在!", username));
}
User user = new User(username, "123456", Arrays.asList());
return user;
}
}
I am so stupid, it's a Cookie(session) problem.
My Authorization Server and Client Server have same domain: localhost, but different port. Authorization Server is 8080, Client Server is 8081.
Client Server login first, has cookie.
Authorization need to login before to approve the Authorization.
When Authorization login,Client's cookie is covered.
When browser redirect to Client's page, Client can't find itself's session with Authorization's cookie.