Every tutorial I see with Spring MVC and AngularJs uses Spring Boot configuration. I am not using Spring Boot so I'm wondering if there are any pointers anybody has on how to handle AngularJS login submission server side WITHOUT using Spring Boot.
So my form is the same basic one from the Spring Security demo here
<div class="alert alert-danger" ng-show="error">
There was a problem logging in. Please try again.
</div>
<form role="form" ng-submit="login()">
<div class="form-group">
<label for="username">Username:</label> <input type="text"
class="form-control" id="username" name="username" ng-model="credentials.username"/>
</div>
<div class="form-group">
<label for="password">Password:</label> <input type="password"
class="form-control" id="password" name="password" ng-model="credentials.password"/>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
The function to authenticate on login() is as follows:
//the authentication function
var authenticate = function(credentials, callback){
var headers =
credentials ?
{
authorization : "Basic " + btoa(credentials.username + ":" + credentials.password)
}
: {};
$http.get('user', {headers : headers}).success(function(data){
if(data.userName){
$rootScope.authenticated = true;
} else {
$rootScope.authenticated = false;
}
callback && callback();
}).error(function(){
$rootScope.authenticated = false;
callback && callback();
});
};
So now the next part is to configure Spring to handle the username and password properties of the credentials object. On the server side, I have a basic WebSecurityConfigurerAdapter:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
DataSource dataSource;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/profile").authenticated()
.anyRequest().permitAll()
.and()
.formLogin()
.loginPage("/signin")
.and()
.logout()
.permitAll();
//.and()
//.requiresChannel()
// .antMatchers("/register").requiresSecure();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
}
}
I know I have to configure a java.security.Principal in there somehow, but like I said, anything I've found has been for spring boot.
Related
I am trying to implement spring security and there are no errors / exceptions, but it is not working, I am not able to redirect to the success page.
My success page is /sucessPage, /Verify will verifying the user details against database.
I want to verify the user with 3 different parameters
SecurityConfig.java
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Resource(name="AdminDB")
private DataSource datasource;
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new Md5PasswordEncoder());
auth.eraseCredentials(false);
}
#Override
protected void configure(HttpSecurity security) throws Exception {
logger.info("Inside SecurityConfig - configure() - Spring security");
security
.authorizeRequests()
.antMatchers("/resources/**", "/unsecured/**", "/","/Verify").permitAll()
.anyRequest().authenticated()
.and().formLogin()
.loginPage("/").permitAll()
.usernameParameter("email")
//.passwordParameter("password")
.defaultSuccessUrl("/sucessPage/")
.successHandler(successhandler())
.failureUrl("/")
.and().logout()
.invalidateHttpSession(true).deleteCookies("JSESSIONID")
.permitAll()
.and().exceptionHandling().accessDeniedPage("/systemError.html")
.and().csrf().disable();
}
#Bean
public CustomAuthenticationSuccessHandler successhandler() {
return new CustomAuthenticationSuccessHandler();
}
}
login page - HTML:
<form id="form" name="Form" action="#" th:action="#{/verify}" th:object="${Form}" method="post">
<div class="50">
<label for="first-name" class="label-first-name">First Name*</label>
<input type="text" id="firstName" name="first-name" placeholder="Enter First Name" th:field="*{firstName}">
</div>
<div class="50">
<label for="last-name" class="label-last-name">Last Name*</label>
<input type="text" id="lastName" name="last-name" placeholder="Enter Last Name" th:field="*{lastName}">
</div>
</div>
<div class="100">
<label for="email-1" class="label-email-address">Email Address*<span class="label-note">This is your login username</span></label>
<input type="text" id="email" name="email" placeholder="Enter Email" th:field="*{email}">
</div>
<div class="100">
<label for="email-2" class="label-email-address">Verify Email Address*</label>
<input type="text" id="verifyEmail" name="verifyEmail" placeholder="Re-enter Email" th:field="*{verifyEmail}">
</div>
<div class="100">
<label for="phone" class="label-phone-number">Phone Number<span class="label-note">(optional)</span></label>
<input type="text" id="phoneNumber" name="phoneNumber" placeholder="Enter Phone Number" th:field="*{phoneNumber}">
</div>
<input type="submit" id="submitBtn" value="Next">
</form>
On submit it should verify user and redirect to the /sucessPage. I just want to restrict users to access other URLs directly and other flow will go as it is.
right now it's redirecting back to "/" without any error.
UserDetailsServiceImpl.java
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private LoginManager loginManager;
#Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException, DataAccessException {
UserDetails user = null;
try {
user = loginManager.findUserByEmail(userName);
} catch (LoginException e) {
throw new UsernameNotFoundException("Username not found: " + e.getMessage());
}
return user;
}
}
loadUserByUsername takes only 1 argument. I want to verify the user by firstName, lastName, email against db and redirecting to success with spring security.Thanks
I think your SecurityConfig.java is incomplete
.formLogin().loginPage("/") is missing :
.loginProcessingUrl("/j_spring_security_check")
and
.defaultSuccessUrl("/sucessPage")
I'm working on a blog application with Spring Boot Security.
My overridden configure method looks like:
#Override
protected void configure(HttpSecurity httpSec) throws Exception {
httpSec
.authorizeRequests()
.antMatchers("/users").authenticated()
.antMatchers("/admin", "/db").hasRole("ADMIN")
.antMatchers("/**").permitAll()
//.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
.and()
.logout().logoutSuccessUrl("/login?logout").permitAll();
httpSec.csrf().disable();
httpSec.headers().frameOptions().disable();
}
And my custom login form:
<form name="login" th:action="#{/login}" method="post" class="form-signin">
<h1>Please log in!</h1>
<div th:if="${param.error}" class="alert alert-danger">Wrong username and/or password.</div>
<div th:if="${param.logout}" class="alert alert-success">You logged out successfully.</div>
<label for="username">Username</label>
<input type="text" name="username" class="form-control" placeholder="username" required="true"/>
<label for="password">Password</label>
<input type="password" name="password" class="form-control" placeholder="password" required="true"/>
<br/>
<button type="submit" class="btn btn-lg btn-primary btn-block">Log in</button>
<br/>
You can register here.
<hr/>
</form>
The default behaviour of Spring Security is that when I send a request to an URL which needs to be authenticated (for example /users or /admin in my case) it automatically redirects to this custom login page.
I would like to disable this automatic redirection. When authentication is needed I would like to throw a custom exception (which I handle with a separate Class with #ControllerAdvice annotation) instead with message like "You have to log in to see this content". But I would like to reach my custom login page via navigation menu to authenticate "manually".
How could I reach this?
So far I have tried .formLogin().disabled(). In this way I can still reach my custom login page, but when I try to submit it gave an error "Method not allowed". But it is logical since th:action="#{/login}" can't send the username and password to /login.
I have found a solution. Maybe not perfect, but it works.
First of all, I have commented out the login/logout part in my configure method:
#Override
protected void configure(HttpSecurity httpSec) throws Exception {
httpSec
.authorizeRequests()
.antMatchers("/users").authenticated()
.antMatchers("/admin", "/db").hasRole("ADMIN")
.antMatchers("/**").permitAll()
/**
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
.and()
.logout().logoutSuccessUrl("/login?logout").permitAll();
*/
.and()
.csrf().disable()
.headers().frameOptions().disable();
}
From now when authentication is needed it won't redirect to any (custom) login screen. But the Forbidden (403) error should be handled:
case "Forbidden":
if (SecurityContextHolder.getContext().getAuthentication().getAuthorities().toString().equals("[ROLE_ANONYMOUS]"))
error.put("error", "You have to log in to see this content");
else error.put("error", "It is only for admins");
break;
Next step is creating a login form into the navigation menu:
<form th:action="#{/loginauth}" method="post">
<span sec:authorize="!isAuthenticated()">
<input type="text" name="email" placeholder="e-mail" required="true"/>
<input type="password" name="password" placeholder="password" required="true"/>
<button type="submit" class="btn btn-success btn-xs">Log in</button>
</span>
</form>
After submit we have to be forwarded to a #PostMapping in one of the #Controller classes:
#PostMapping("/loginauth")
public String authenticateLogin(HttpServletRequest request) {
loginService.authenticateBlogUser(request.getParameter("email"), request.getParameter("password"));
return "redirect:/";
}
Finally this data should be sent to a service layer to process:
BlogUserRepository blogUserRepository;
#Autowired
public void setBlogUserRepository(BlogUserRepository blogUserRepository) {
this.blogUserRepository = blogUserRepository;
}
public void authenticateBlogUser(String email, String password) throws UsernameNotFoundException {
BlogUser user = blogUserRepository.findByEmail(email);
if (user == null || !user.getPassword().equals(password))
throw new UsernameNotFoundException("Wrong e-mail and/or password");
Collection<GrantedAuthority> authorities = new HashSet<>();
Set<Role> roles = user.getRoles();
for (Role role : roles)
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getAuth()));
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(user.getEmail(), user.getPassword(), authorities));
}
I want to have more control over the logging in and out, via custom controller and login page.
My SecurityConfiguration code currently looks like this:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private SpringDataJpaUserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(this.userDetailsService)
.passwordEncoder(Manager.PASSWORD_ENCODER);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/resources/**", "/built/**", "/main.css", "/login.css").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/loginSecure")
.defaultSuccessUrl("/index", true)
.permitAll()
.usernameParameter("username").passwordParameter("password")
.and()
.csrf().disable()
.logout()
.permitAll();
}
}
My login config in my Controller:
#RequestMapping(value = "/login")
public String login() {
return "login";
}
My loginSecure mapping in my controller:
#RequestMapping(value="/loginSecure", method = RequestMethod.POST)
public String login(#RequestAttribute("username") String userName, #RequestAttribute("password") String password) {
//does the authentication
final Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
userName,
password
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
return "index";
}
My login.html:
<form class="login100-form validate-form" action="/loginSecure" method="post">
<span class="login100-form-title p-b-26">
Welcome
</span>
<span class="login100-form-title p-b-48">
<i class="zmdi zmdi-font"></i>
</span>
<div class="wrap-input100 validate-input" data-validate = "Valid email is: a#b.c">
<input class="input100" type="text" id="username" name="username"/>
<span class="focus-input100" data-placeholder="Email/Username"></span>
</div>
<div class="wrap-input100 validate-input" data-validate="Enter password">
<span class="btn-show-pass">
<i class="zmdi zmdi-eye"></i>
</span>
<input class="input100" type="password" id="password" name="password"/>
<span class="focus-input100" data-placeholder="Password"></span>
</div>
<div class="container-login100-form-btn">
<div class="wrap-login100-form-btn">
<div class="login100-form-bgbtn"></div>
<button class="login100-form-btn">
Login
</button>
</div>
</div>
</form>
When i submit the form, in chrome dev tools it submits as loginSecure? with url encoded but it just redirects back to the login.html again.
Edit: Removed the extra form from login.html and added csfr().disable to securityConfiguration. Added loginProcessUrl to httpSecurity and this fixed it. Above code works.
If you create a custom login html and a custom authenticator then you need to add this to the HttpSecurity config -> .loginProcessingUrl("/loginSecure")
Good example here -> https://www.boraji.com/spring-security-4-custom-login-from-example
From what you wrote I guess that the problem is that after clicking "Login" your application is hit by two request.
I think that problem is that your login page has two forms one inside another. So when you click "Login" both forms sends their requests. You can verify that in Chrome Developer Tools.
As you can read here HTML doesn't allow nested forms Is it valid to have a html form inside another html form?
I've generated a Spring Boot web application using Spring Initializer, embedded Tomcat, Thymeleaf template engine, and package as an executable JAR file.
Technologies used:
Spring Boot 1.4.2.RELEASE, Spring 4.3.4.RELEASE, Thymeleaf 2.1.5.RELEASE, Tomcat Embed 8.5.6, Maven 3, Java 8
This is my security config class:
#Configuration
#EnableWebSecurity
#PropertySource("classpath:/config/app-${APP-KEY}.properties")
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${securityConfig.formLogin.loginPage}")
private String loginPage;
#Bean
public StandardPasswordEncoder encoder() {
return new StandardPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginPage(loginPage)
.permitAll()
.loginProcessingUrl("/tdk/login")
.failureUrl("/tdk/login?error=true")
.defaultSuccessUrl("/events/list")
.and()
.exceptionHandling()
.accessDeniedPage("/denied")
.and()
.authorizeRequests()
.antMatchers("/resources/**").permitAll()
.antMatchers("/mockup/**").permitAll()
.antMatchers("/users/**").permitAll()
.antMatchers("/books/**").permitAll()
.antMatchers("/welcome/**").authenticated()
.and()
.logout()
.permitAll()
.logoutSuccessUrl("/index.html");
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.passwordEncoder(new StandardPasswordEncoder())
.withUser("test1").password("c1f02fa50809b7f715576198eda6466cd17f63404ae6eded7c22290b025baf3868bc8f785267d4ae").roles("ADMIN").and()
.withUser("test2").password("test2").roles("USER").and()
.withUser("test3").password("test3").roles("SUPERADMIN");
}
#Bean
public static PropertySourcesPlaceholderConfigurer propertyDefaultConfig() {
return new PropertySourcesPlaceholderConfigurer();
}
}
This is my Junit Tests that works properly
public class StandardPasswordEncoderTests {
#Test
public void getPasswordForTest1() {
StandardPasswordEncoder encoder = new StandardPasswordEncoder();
String password = "test1";
assertTrue(
encoder.matches(password, "c1f02fa50809b7f715576198eda6466cd17f63404ae6eded7c22290b025baf3868bc8f785267d4ae"));
}
}
Here my login template
<form th:action="#{/tdk/login}" method="post">
<p th:if="${param.error}">
Bad Credentials ${param.error}
</p>
<p th:if="${loginError}" class="error">Wrong user or password</p>
<div class="input_label"><i class="fa fa-user"></i><input type="text" name="user" placeholder="User" /></div>
<div class="input_label"><i class="fa fa-key"></i><input type="password" name="pass" placeholder="Password" /></div>
<input type="submit" value="LOGIN" />
</form>
But whatever I put:
test1 / c1f02fa50809b7f715576198eda6466cd17f63404ae6eded7c22290b025baf3868bc8f785267d4ae
or
test2 / test2
I see the message Bad Credentials ${param.error} in the output of my template
The parameter names for username and password in your login page are not matching the names in Spring Security configuration.
You could change the Spring Security configuration to use the parameter names from your login page. Or you could change the login page to use the default parameter names.
See FormLoginConfigurer#usernameParameter:
The HTTP parameter to look for the username when performing authentication. Default is "username".
and FormLoginConfigurer#passwordParameter:
The HTTP parameter to look for the password when performing authentication. Default is "password".
Your modified login page (with default parameter names):
<form th:action="#{/tdk/login}" method="post">
<p th:if="${param.error}">
Bad Credentials ${param.error}
</p>
<p th:if="${loginError}" class="error">Wrong user or password</p>
<div class="input_label">
<i class="fa fa-user"></i>
<input type="text" name="username" placeholder="User" />
</div>
<div class="input_label">
<i class="fa fa-key"></i>
<input type="password" name="password" placeholder="Password" />
</div>
<input type="submit" value="LOGIN" />
</form>
I have a webapp and I am using Spring Security. I have my Spring Security configure function in security.config as:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/static/**").permitAll()
.antMatchers("/settings/api/**").permitAll()
.antMatchers("/api/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
.defaultSuccessUrl("/", true);
}
The problem I am having is this always gets mapped to #RequestMapping(value="/login", method=RequestMethod.GET) instead of #RequestMapping(value="/login", method=RequestMethod.POST). So whenever I am trying to retrieve username and password from Http Request it comes out as null. How do I get it to map to POST method?
I have my post controller as
#RequestMapping(value="/login", method=RequestMethod.POST)
public String login(HttpServletRequest request, HttpServletResponse response) {
String username = request.getParameter("username");
String password = request.getParameter("password");
boolean f = false;
f = customAuthenticationProvider.verifyUser(username,password,request);
if(f == false)
return "loginError";
else
return "index";
}
with formlogin() you don't have to provide the POST controller, that's being taken care of by spring on the UsernamePasswordAuthenticationFilter. What you are configuring it's just the loginPage("/login") on which you have to add a form with a POST action to "/login", and add username and password inputs.
Something like this:
<form action="login" method="post">
<div>
<label for="username">Username:</label>
<input type="text" class="form-control" id="username" name="username"/>
</div>
<div>
<label for="password">Password:</label>
<input type="password" class="form-control" id="password" name="password"/>
</div>
<button type="submit">Submit</button>
</form>