Hello i have been trying to configure spring to have it return JWT token if user/pass is authenticated to LDAP Server; Consider the use case below ;
On the above diagram, i have configured WebSecurity to check/filter out requests with Bearer. See code below
WebSecurityConfig.java
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
#Autowired
JwtAuthorizationTokenFilter authenticationTokenFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
// Configure Web Security
// Allow only /auth/
// Disallow all others
http
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(HttpMethod.POST,
"/auth/**")
.permitAll()
.anyRequest().authenticated();
//Custom JWT
http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// disable page caching
http.headers().cacheControl();
}
}
AuthCtrl.java
#RestController
#RequestMapping("auth")
public class AuthCtrl {
private static final Logger logger = LoggerFactory.getLogger(AuthCtrl.class);
#Autowired
#Qualifier("authenticationManagerImpl")
private AuthenticationManager authenticationManager;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Autowired
#Qualifier("userDetailsServiceImpl")
private UserDetailsService userDetailsService;
#PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody String post(#RequestBody Map<String, String> credentials) {
logger.info("POST: {} | {} ",credentials.get("username"), credentials.get("password"));
String username = credentials.get("username");
String password = credentials.get("password");
Objects.requireNonNull(username);
Objects.requireNonNull(password);
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
// Reload password post-security so we can generate the token
final UserDetails userDetails = userDetailsService.loadUserByUsername(username);
final String token = jwtTokenUtil.generateToken(userDetails);
return token;
} catch (DisabledException e) {
throw new AuthenticationException("User is disabled!", e);
} catch (BadCredentialsException e) {
throw new AuthenticationException("Bad credentials!", e);
}
}
#ExceptionHandler({AuthenticationException.class})
public ResponseEntity<String> handleAuthenticationException(AuthenticationException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage());
}
}
Above configuration was based on a youtube guide i've seen and also a pull from a demo source in git. Great help!, credits to the owners. Got to understand how filters work somehow.
The above source can already filter out all protected API and sends out unauthorized back as a response when it is not authorized. The only api i allowed to be accessed anonymously is the authentication api /auth. It can already receive the request and passed through the web filters.
But i can't quite figure out how to authenticate the said request to LDAP server and sends out a JWT token. On the guide i've read they are getting the user information on a database.
I've read some documentation on LDAP configuration in WebConfiguration, but i can't relate it to my current filters.
Please check the below link I have created it using spring 4.
Instead of .ldif file on classpath configure your own ldap server.
https://github.com/merugu/springsecurity/tree/master/ldapauthenticationjwttoken
The only differences is for Spring 5 you should use
advance password encoding algorithm like Bcryptpasswordencoder.As the LDAPpasswordEncoder is deprecated.
Happy coding!
Related
So I have this project, which really all I want to do is be able to have a user log in and get access to a specific page:
Security
#Configuration
#EnableWebSecurity
public class MainSecurityConfig extends WebSecurityConfigurerAdapter {
#Resource
private UserDetailsServiceImpl userDetailsService;
#Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable();
http
.authorizeRequests()
.antMatchers("/", "/**", "/login/**", "/index.html", "/login.html", "/components/**", "/css/**", "/js/**", "/fonts/**", "/images/**", "/.sass-cache/**", "/services.html").permitAll()
.anyRequest().authenticated();
http.formLogin()
.loginPage("/login")
.failureForwardUrl("/login.html")
.usernameParameter("user")
.passwordParameter("password");
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
When I sent the /login request from angularjs as a POST I was hitting the UserDetailsServiceImpl which is good, but the username was coming in empty.
UserDetailsServiceImpl
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
#Resource
private HttpSession httpSession;
#Resource
private UserDao userDao;
#Override
public UserDetails loadUserByUsername(String user) throws UsernameNotFoundException {
User userByEmail = userDao.findUserByEmail(user);
UserDetailsImpl userDetails = new UserDetailsImpl(userByEmail, httpSession.getId());
return userDetails;
}
}
So I did some googling and it said that the /login request has to be GET, which in itself confused me, should we really be plonking the username and password into the url? Or am I thinking about this wrong. Anyway, here's the angularJS code:
$scope.loginUser = function () {
$scope.user.user = $scope.email;
$scope.user.password = $scope.password;
$http.get("/login", { params: {username: $scope.user.user, password: $scope.user.password}});
I no longer hit the breakpoints now within UserDetailsServiceImpl and rather I am getting a 404.
UPDATE
After updating the processing url, I now post it but the username that get's passed server-side is empty
$scope.loginUser = function () {
$scope.user.username = $scope.email;
$scope.user.password = $scope.password;
$http.post("/api/authentication", $scope.user);
Everything up to here is fine, it's just when java handles it
If you are using Angular you have not a loginPage because you are writing a SPA and page navigation is managed by Angular itself.
You should use loginProcessingUrl that defines only the login submission url
.and()
.formLogin()
.loginProcessingUrl("/api/authentication")
.usernameParameter("username")
.passwordParameter("password")
To submit you login you need to do a POST not a GET. Probably links mean url to access a login page not to submit.
In the example above you have to do a POST using url /api/authentication with a body containing username and password
Also if i've seen you already found the solution, i've published a project based on Spring Boot 2.0 and angular 6 (angularjs is quite outdated) with spring security and a stateful authentication (the same you were searching for)
https://github.com/ValerioMC/vforge-stateful-auth
It's just a starting point.
The issue was with my AngularJS $http.post request, I solved it by adding headers for 'application/x-www-form-urlendcoded' as it is not the default header:
$http({
method: 'POST',
url: '/api/authentication',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
data: 'username=' + $scope.email + '&password=' + $scope.password
});
I'm creating a spring boot 2.0 application and trying to enable oauth2 security. I have Auth server and Resource server in the same application as of now. My client and user details as well as token generated are persisted in databases (mysql) and database schema is the same as provided by spring documentation. When I hit the '/oauth/token/' endpoint providing clientId and clientSecret in header and user's credentials in body using Postman, I'm getting access token successfully.
{
"access_token": "bef2d974-2e7d-4bf0-849d-d80e8021dc50",
"token_type": "bearer",
"refresh_token": "32ed6252-e7ee-442c-b6f9-d83b0511fcff",
"expires_in": 6345,
"scope": "read write trust"
}
But when I try to hit my rest api using this access token, I'm getting 401 Unauthorized error:
{
"timestamp": "2018-08-13T11:17:19.813+0000",
"status": 401,
"error": "Unauthorized",
"message": "Unauthorized",
"path": "/myapp/api/unsecure"
}
The rest APIs I'm hitting are as follows:
http://localhost:8080/myapp/api/unsecure
http://localhost:8080/myapp/api/secure
myapp is the context path of my application.
For 'secure' api, I have provided access token in request header as described in Spring documentation:
Authorization: Bearer bef2d974-2e7d-4bf0-849d-d80e8021dc50
Whereas for unsecure api, I have tried with and without Authentication header. In all cases I'm getting same error for both apis.
Also when I try to print currently authenticated user, its getting printed as anonymousUser.
What I want are as follows:
1) I want my secure api to be accessible only when access token is provided in request header.
2) I want my unsecure api to be accessible by unauthorised user.
3) I should get currently authenticated user using SecurityContextHolder when accessing secure url.
My WebSecurityConfigurerAdapter is as follows:
#Configuration
#EnableWebSecurity(debug=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Autowired
UserDetailsService userDetailsService;
#Autowired
private ClientDetailsService clientDetailsService;
#Bean
public PasswordEncoder userPasswordEncoder() {
return new BCryptPasswordEncoder(8);
}
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws
Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(userPasswordEncoder());
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws
Exception {
return super.authenticationManagerBean();
}
#Bean
#Autowired
public TokenStoreUserApprovalHandler userApprovalHandler(TokenStore
tokenStore){
TokenStoreUserApprovalHandler handler = new
TokenStoreUserApprovalHandler();
handler.setTokenStore(tokenStore);
handler.setRequestFactory(new
DefaultOAuth2RequestFactory(clientDetailsService));
handler.setClientDetailsService(clientDetailsService);
return handler;
}
#Bean
#Autowired
public ApprovalStore approvalStore(TokenStore tokenStore) throws
Exception {
TokenApprovalStore store = new TokenApprovalStore();
store.setTokenStore(tokenStore);
return store;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.cors().disable()
.anonymous().disable()
.authorizeRequests()
.antMatchers("/index.html", "/**.js", "/**.css", "/").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic();
}
Here using antMatchers I have permitted static pages of Angular 6 application, as I'm planning to use those in my real app. And no, the following line does not work to allow static pages of angular application:
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
My AuthorizationServerConfigurerAdapter is as follows:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends
AuthorizationServerConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Autowired
UserDetailsService userDetailsService;
#Autowired
PasswordEncoder passwordEncoder;
#Autowired
TokenStore tokenStore;
#Autowired
private UserApprovalHandler userApprovalHandler;
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
oauthServer
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.passwordEncoder(passwordEncoder);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(tokenStore)
.userApprovalHandler(userApprovalHandler)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
}
My ResourceServerConfigurerAdapter is as follows:
#Configuration
#EnableResourceServer
public abstract class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID = "resource-server-rest-api";
#Autowired
TokenStore tokenStore;
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources
.resourceId(RESOURCE_ID)
.tokenStore(tokenStore);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.cors().disable()
.anonymous().disable()
.requestMatchers()
.antMatchers("/api/**").and()
.authorizeRequests()
.antMatchers("/api/secure").authenticated()
.antMatchers("/api/unsecure").permitAll();
}
}
But when I enable anonymous access in SecurityConfig and declare my unsecure url as permitAll, then I'm able to access that url.
.antMatchers("/api/unsecure", "/index.html", "/**.js", "/**.css", "/").permitAll()
My Controller class is as follows:
#RestController
#RequestMapping("/api")
public class DemoController {
#GetMapping("/secure")
public void sayHelloFriend() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
System.out.println("Current User: "+authentication.getName());
System.out.println("Hello Friend");
}
#GetMapping("/unsecure")
public void sayHelloStranger() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
System.out.println("Current User: "+authentication.getName());
System.out.println("Hello Stranger");
}
}
Let me know if any more information is needed. Any help will be appreciated. But please keep in mind that its Spring Boot 2.0 not 1.5 as both have some critical differences as per my findings.
Try to added
#Order(SecurityProperties.BASIC_AUTH_ORDER)
for the securityConfig? so the chain will check your Resource server's config first.
And not sure if that your type error, remove the abstract from the resource server.
I'm trying to secure a simple Spring-Data-Rest app using jwt.
Taking the seed from https://github.com/spring-projects/spring-data-examples/tree/master/rest/security
The SecurityConfig is below (using normal username, password authentication)
How can I change this to JWT Authentication?
(Authorization is already done using #PreAuthorize("hasRole('ROLE_USER')") in repositories)
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
/**
* This section defines the user accounts which can be used for
* authentication as well as the roles each user has.
*/
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("greg").password("turnquist").roles("USER").and()
.withUser("ollie").password("gierke").roles("USER", "ADMIN");
}
/**
* This section defines the security policy for the app.
* - BASIC authentication is supported (enough for this REST-based demo)
* - /employees is secured using URL security shown below
* - CSRF headers are disabled since we are only testing the REST interface,
* not a web one.
*
* NOTE: GET is not shown which defaults to permitted.
*/
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().and()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/employees").hasRole("ADMIN")
.antMatchers(HttpMethod.PUT, "/employees/**").hasRole("ADMIN")
.antMatchers(HttpMethod.PATCH, "/employees/**").hasRole("ADMIN").and()
.csrf().disable();
}
}
here is a good tutorial for JWT Authentication in spring boot, but in can applied for spring applications as well: https://auth0.com/blog/implementing-jwt-authentication-on-spring-boot/
According to the tutorial in your SecurityConfiguration.configure you need
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
// this disables session creation on Spring Security
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter, is applied to /login URL and generates JWT token based on your login/password if such a user exists in the system.
JWTAuthorizationFilter verifies JWT token coming in http header
Of course you need to add more moving parts in order to enable JWT auth by this tutorial.
I followed the tutorial for Spring Security OAuth:
https://projects.spring.io/spring-security-oauth/docs/oauth2.html
In particular you have to enable the resource server. This is my (modified) configuration):
#EnableResourceServer
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenServices(tokenServices());
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
try {
// Load the public key of the authorization server.
String key = IOUtils.toString(getClass().getResource("/reng0-public.key"), Charset.forName("US-ASCII"));
converter.setVerifierKey(key);
} catch (IOException e) {
throw new RuntimeException(e);
}
return converter;
}
#Bean
#Primary
public ResourceServerTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
return defaultTokenServices;
}
}
The client has to add the Authorization: Bearer header to make it work.
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.
I'm using Spring security and database login in my application (in the future I'll have to implement LDAP authentication).
Through web all work right, but now when I call web services from external (I have some web service for internal javascript and some for external calls) I receive the HTML code of login page. It's correct, but now how can I make REST call?
I have to protect them, I thought to use a token or username and password for each web services call, but how can I set username and password in REST call?
For example with postman. Then I will set the credentials also in
restTemplate.setRequestFactory(requestFactory);
responseEntity = restTemplate.getForEntity(serverIp + "ATS/client/file/?filePath={filePath}", byte[].class, filePath);
and in
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
ContentBody cbFile = new FileBody(file);
ContentBody cbPath= new StringBody(toStorePath,ContentType.TEXT_PLAIN);
builder.addPart("file", cbFile);
builder.addPart("toStorePath",cbPath);
httppost.setEntity(builder.build());
CloseableHttpResponse httpResponse = httpClient.execute(httppost);
HttpEntity resEntity = httpResponse.getEntity();
On the web I have even the roles for the user, maybe I'll have to use them also for the web services.
Thanks for the advices. Regards
UPDATE:
As #Gergely Bacso advices me, I have updated my code, but now I have the opposite problems: When I call web services they return all the information without username and password.
This is security config:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, proxyTargetClass = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("userDetailsService")
UserDetailsService userDetailsService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder(){
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.antMatcher("/client/**")
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
}
#Configuration
#Order(2)
public static class FormWebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
public void configure(WebSecurity web) throws Exception {
web
//Spring Security ignores request to static resources such as CSS or JS files.
.ignoring()
.antMatchers("/static/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests() //Authorize Request Configuration
//.antMatchers("/", "/register").permitAll()
// .antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and() //Login Form configuration for all others
.formLogin()
.loginPage("/login").permitAll();
}
}
}
There was a similar question asked only a few days ago:
Securing REST service with Spring Security
The important part is that:
In case you want to secure something that is accessed programatically (for example a REST service being called by another program) then you should not use form-based authentication.
What you need is something much more suitable for the job. Like an HTTP-basic auth. Form-based login methods are more suited to use cases where users can enter their username/password.