J'ai une application dans spring/spring-mvc qui utilise totalement les communications JSON ..__ Je dois maintenant authentifier mon application avec Spring Security 3 (qui utilise LdapAuthenticationProvider) via JSON.
Le formulaire de soumission Spring Seurity par défaut nécessite a POST comme ceci:
POST /myapp/j_spring_security_check HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 32
Host: 127.0.0.1:8080
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (Java 1.5)
j_username=myUsername&j_password=myPass
Mais je veux passer un objet JSON comme ceci:
{"j_username":"myUsername","j_password":"myPass"}
Je lis beaucoup de posts comme this , cet autre ou celui-ci sans chance, dans tous les cas ajax se fait a POST comme ci-dessus.
Des idées?
Vous pouvez écrire votre propre filtre de sécurité qui analysera votre JSON.
http://docs.spring.io/spring-security/site/docs/3.0.x/reference/core-web-filters.html
Vous pouvez utiliser le BasicAuthenticationFilter comme référence:
Selon les suggestions de Kevin,
et après avoir lu ce billet: 1 , 2 , documentation 3 , et merci à this blog post,
J'ai écrit mon propre FORM_LOGIN_FILTER pour gérer directement JSON avant l'authentification.
Je colle mon code pour la communauté.
L’objectif est d’autoriser l’authentification à la fois par le navigateur classique POST et l’authentification basée sur JSON. De plus, dans l'authentification JSON, je souhaite éviter la redirection vers loginSuccesful.htm.
Dans le contexte:
<security:http use-expressions="true" auto-config="false" entry-point-ref="http403EntryPoint">
<security:intercept-url pattern="/logs/**" access="denyAll" />
<!-- ... All other intercept URL -->
<security:custom-filter ref="CustomUsernamePasswordAuthenticationFilter" position="FORM_LOGIN_FILTER "/>
<security:logout
invalidate-session="true"
logout-success-url="/LogoutSuccessful.htm"
delete-cookies="true"
/>
<security:session-management>
<security:concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</security:session-management>
<security:access-denied-handler error-page="/accessDenied.htm" />
</security:http>
<bean id="CustomUsernamePasswordAuthenticationFilter" class="path.to.CustomUsernamePasswordAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager" />
<property name="authenticationSuccessHandler" ref="customSuccessHandler"/>
<property name="authenticationFailureHandler" ref="failureHandler"/>
<property name="filterProcessesUrl" value="/j_spring_security_check"/>
<property name="usernameParameter" value="j_username"/>
<property name="passwordParameter" value="j_password"/>
</bean>
<bean id="customSuccessHandler" class="path.to.CustomAuthenticationSuccessHandler">
<property name="defaultTargetUrl" value="/login.htm" />
<property name="targetUrlParameter" value="/LoginSuccessful.htm" />
</bean>
<bean id="failureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<property name="defaultFailureUrl" value="/login.htm" />
</bean>
<bean id="http403EntryPoint" class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint" />
CustomUsernamePasswordAuthenticationFilter classe:
public class CustomUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter{
private String jsonUsername;
private String jsonPassword;
@Override
protected String obtainPassword(HttpServletRequest request) {
String password = null;
if ("application/json".equals(request.getHeader("Content-Type"))) {
password = this.jsonPassword;
}else{
password = super.obtainPassword(request);
}
return password;
}
@Override
protected String obtainUsername(HttpServletRequest request){
String username = null;
if ("application/json".equals(request.getHeader("Content-Type"))) {
username = this.jsonUsername;
}else{
username = super.obtainUsername(request);
}
return username;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response){
if ("application/json".equals(request.getHeader("Content-Type"))) {
try {
/*
* HttpServletRequest can be read only once
*/
StringBuffer sb = new StringBuffer();
String line = null;
BufferedReader reader = request.getReader();
while ((line = reader.readLine()) != null){
sb.append(line);
}
//json transformation
ObjectMapper mapper = new ObjectMapper();
LoginRequest loginRequest = mapper.readValue(sb.toString(), LoginRequest.class);
this.jsonUsername = loginRequest.getUsername();
this.jsonPassword = loginRequest.getPassword();
} catch (Exception e) {
e.printStackTrace();
}
}
return super.attemptAuthentication(request, response);
}
}
Classe CustomAuthenticationSuccessHandler:
public class CustomAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
public void onAuthenticationSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication auth
)throws IOException, ServletException {
if ("application/json".equals(request.getHeader("Content-Type"))) {
/*
* USED if you want to AVOID redirect to LoginSuccessful.htm in JSON authentication
*/
response.getWriter().print("{\"responseCode\":\"SUCCESS\"}");
response.getWriter().flush();
} else {
super.onAuthenticationSuccess(request, response, auth);
}
}
}
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response){
if (!request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
LoginRequest loginRequest = this.getLoginRequest(request);
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
private LoginRequest getLoginRequest(HttpServletRequest request) {
BufferedReader reader = null;
LoginRequest loginRequest = null;
try {
reader = request.getReader();
Gson gson = new Gson();
loginRequest = gson.fromJson(reader, LoginRequest.class);
} catch (IOException ex) {
Logger.getLogger(AuthenticationFilter.class.getName()).log(Level.SEVERE, null, ex);
} finally {
try {
reader.close();
} catch (IOException ex) {
Logger.getLogger(AuthenticationFilter.class.getName()).log(Level.SEVERE, null, ex);
}
}
if (loginRequest == null) {
loginRequest = new LoginRequest();
}
return loginRequest;
}
}
Si vous souhaitez simplement un analyseur de corps de requête différent pour la demande de connexion, étendez simplement UsernamePasswordAuthenticationFilter
et substituez la méthode attemptAuthentication
. Par défaut, UsernamePasswordAuthenticationFilter
analysera les données encodées dans l'URL et créera UsernamePasswordAuthenticationToken
. Maintenant, il vous suffit de créer un analyseur syntaxique qui analysera tout ce que vous envoyez à l'application.
Voici un exemple qui analysera {"username": "someusername", "password": "somepassword"}
public class CustomUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
BufferedReader reader = request.getReader();
StringBuffer sb = new StringBuffer();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
String parsedReq = sb.toString();
if (parsedReq != null) {
ObjectMapper mapper = new ObjectMapper();
AuthReq authReq = mapper.readValue(parsedReq, AuthReq.class);
return new UsernamePasswordAuthenticationToken(authReq.getUsername(), authReq.getPassword());
}
} catch (Exception e) {
System.out.println(e.getMessage());
throw new InternalAuthenticationServiceException("Failed to parse authentication request body");
}
return null;
}
@Data
public static class AuthReq {
String username;
String password;
}
}
Dans le fragment, le corps de la demande est extrait en chaîne et mappé à l'objet AuthReq
(l'annotation @Data
est issue de lombok lib, elle générera des séparateurs et des getters) . Vous pouvez alors créer UsernamePasswordAuthenticationToken
qui sera passé à la valeur par défaut AuthenticationProvider
.
Vous pouvez maintenant étendre WebSecurityConfigurerAdapter
et remplacer la méthode cnofigure pour remplacer l’ancien filtre.
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/login", "/logout").permitAll()
.anyRequest().authenticated()
.and().addFilterAt(new CustomUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.formLogin().loginProcessingUrl("/login")
.and()
.csrf().disable();
}
Avec la méthode addFilterAt
, vous remplacez la valeur par défaut UsernamePasswordAuthenticationFilter
. N'oubliez pas d'utiliser l'annotation @EnableWebSecurity
.
Un autre moyen, selon this post, consiste à gérer manuellement l’authentification de sécurité du ressort directement dans le contrôleur.
De cette manière, il est très simple de gérer les entrées JSON et d’éviter la redirection de connexion:
@Autowired
AuthenticationManager authenticationManager;
@ResponseBody
@RequestMapping(value="/login.json", method = RequestMethod.POST)
public JsonResponse mosLogin(@RequestBody LoginRequest loginRequest, HttpServletRequest request) {
JsonResponse response = null;
try {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());
token.setDetails(new WebAuthenticationDetails(request));
Authentication auth = authenticationManager.authenticate(token);
SecurityContext securityContext = SecurityContextHolder.getContext();
securityContext.setAuthentication(auth);
if(auth.isAuthenticated()){
HttpSession session = request.getSession(true);
session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);
LoginResponse loginResponse = new LoginResponse();
loginResponse.setResponseCode(ResponseCodeType.SUCCESS);
response = loginResponse;
}else{
SecurityContextHolder.getContext().setAuthentication(null);
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setResponseCode(ResponseCodeType.ERROR);
response = errorResponse;
}
} catch (Exception e) {
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setResponseCode(ResponseCodeType.ERROR);
response = errorResponse;
}
return response;
}
J'ai appliqué les réponses de fl4l et oe.elvik pour la connexion avec des informations d'identification JSON dans une application Spring Boot. Je travaille avec une configuration de bean basée sur des annotations.
Dans les réponses référencées, un filtre personnalisé est créé dans lequel le gestionnaire d'authentification est injecté. Pour ce faire, le gestionnaire d'authentification doit être présent en tant que bean Spring. Voici un lien sur la façon de le faire: https://stackoverflow.com/a/21639553/3950535 .
Voici la configuration Java pour les solutions ci-dessus:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.addFilterBefore(authenticationFilter(),UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll();
}
@Bean
public AuthenticationFilter authenticationFilter() throws Exception{
AuthenticationFilter authenticationFilter = new AuthenticationFilter();
authenticationFilter.setUsernameParameter("username");
authenticationFilter.setPasswordParameter("password");
authenticationFilter.setAuthenticationManager(authenticationManager());
authenticationFilter.setFilterProcessesUrl("/login");
authenticationFilter.setAuthenticationSuccessHandler(successHandler());
return authenticationFilter;
}
@Bean
public SuccessHandler successHandler(){
return new SuccessHandler();
}
Regardez cet exemple: https://github.com/fuhaiwei/springboot_security_restful_api
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private CustomLoginHandler customLoginHandler;
@Autowired
private CustomLogoutHandler customLogoutHandler;
@Autowired
private CustomAccessDeniedHandler customAccessDeniedHandler;
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.antMatchers("/api/basic/**").hasRole("BASIC")
.antMatchers("/api/session").permitAll()
.antMatchers(HttpMethod.GET).permitAll()
.antMatchers("/api/**").hasRole("BASIC");
http.formLogin();
http.logout()
.logoutUrl("/api/session/logout")
.addLogoutHandler(customLogoutHandler)
.logoutSuccessHandler(customLogoutHandler);
http.exceptionHandling()
.accessDeniedHandler(customAccessDeniedHandler)
.authenticationEntryPoint(customAccessDeniedHandler);
http.csrf()
.ignoringAntMatchers("/api/session/**");
http.addFilterBefore(new AcceptHeaderLocaleFilter(), UsernamePasswordAuthenticationFilter.class);
http.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
http.addFilterAfter(new CsrfTokenResponseHeaderBindingFilter(), CsrfFilter.class);
}
private CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
filter.setAuthenticationSuccessHandler(customLoginHandler);
filter.setAuthenticationFailureHandler(customLoginHandler);
filter.setAuthenticationManager(authenticationManager());
filter.setFilterProcessesUrl("/api/session/login");
return filter;
}
private static void responseText(HttpServletResponse response, String content) throws IOException {
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
byte[] bytes = content.getBytes(StandardCharsets.UTF_8);
response.setContentLength(bytes.length);
response.getOutputStream().write(bytes);
response.flushBuffer();
}
@Component
public static class CustomAccessDeniedHandler extends BaseController implements AuthenticationEntryPoint, AccessDeniedHandler {
// NoLogged Access Denied
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
responseText(response, errorMessage(authException.getMessage()));
}
// Logged Access Denied
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
responseText(response, errorMessage(accessDeniedException.getMessage()));
}
}
@Component
public static class CustomLoginHandler extends BaseController implements AuthenticationSuccessHandler, AuthenticationFailureHandler {
// Login Success
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
LOGGER.info("User login successfully, name={}", authentication.getName());
responseText(response, objectResult(SessionController.getJSON(authentication)));
}
// Login Failure
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
responseText(response, errorMessage(exception.getMessage()));
}
}
@Component
public static class CustomLogoutHandler extends BaseController implements LogoutHandler, LogoutSuccessHandler {
// Before Logout
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
}
// After Logout
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
responseText(response, objectResult(SessionController.getJSON(null)));
}
}
private static class AcceptHeaderLocaleFilter implements Filter {
private AcceptHeaderLocaleResolver localeResolver;
private AcceptHeaderLocaleFilter() {
localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(Locale.US);
}
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
Locale locale = localeResolver.resolveLocale((HttpServletRequest) request);
LocaleContextHolder.setLocale(locale);
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
}
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
UsernamePasswordAuthenticationToken authRequest;
try (InputStream is = request.getInputStream()) {
DocumentContext context = JsonPath.parse(is);
String username = context.read("$.username", String.class);
String password = context.read("$.password", String.class);
authRequest = new UsernamePasswordAuthenticationToken(username, password);
} catch (IOException e) {
e.printStackTrace();
authRequest = new UsernamePasswordAuthenticationToken("", "");
}
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}