Spring Security 和 JSON 身份验证
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19500332/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Spring Security and JSON Authentication
提问by fl4l
I've an application in spring/spring-mvc that totally uses JSON communications. Now I need to authenticate my application with spring security 3 (that uses LdapAuthenticationProvider) via JSON.
我在 spring/spring-mvc 中有一个完全使用 JSON 通信的应用程序。现在我需要通过 JSON 使用 spring security 3(使用 LdapAuthenticationProvider)对我的应用程序进行身份验证。
The default spring seurity submit form requires a POST like this:
默认的 spring seurity 提交表单需要这样的 POST:
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
But I want to pass a JSON object like this:
但我想传递这样的 JSON 对象:
{"j_username":"myUsername","j_password":"myPass"}
I read many post like this, this otheror this onewithout luck, in all ajax cases is done a POST like above.
我看了很多帖子喜欢这样,这样其他的或这一个没有运气,在所有的Ajax的情况下完成一个POST像上面。
Any Ideas?
有任何想法吗?
采纳答案by Kevin Bayes
You can write your own security filter that will parse your JSON.
您可以编写自己的安全过滤器来解析您的 JSON。
http://docs.spring.io/spring-security/site/docs/3.0.x/reference/core-web-filters.html
http://docs.spring.io/spring-security/site/docs/3.0.x/reference/core-web-filters.html
You can use the BasicAuthenticationFilter as a reference:
您可以使用 BasicAuthenticationFilter 作为参考:
回答by fl4l
According with Kevin suggestions,
and after reading this posts: 1, 2, documentation 3, and thanks to thisblog post,
I wrote my own FORM_LOGIN_FILTER to directly manage JSON before authentication.
I paste my code for the community.
与凯文的建议根据,
并阅读本帖后:1,2,文件3,并感谢该博客文章,
我写我自己FORM_LOGIN_FILTER来验证之前直接管理JSON。
我为社区粘贴了我的代码。
The goal is to grant both the classical browser form POST authentication with JSON based authentication. Also in JSON authentication I want to avoid the redirect to loginSuccesful.htm
目标是授予经典浏览器表单 POST 身份验证和基于 JSON 的身份验证。同样在 JSON 身份验证中,我想避免重定向到 loginSuccesful.htm
In context:
在上下文中:
<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 class:
CustomUsernamePasswordAuthenticationFilter 类:
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);
}
}
CustomAuthenticationSuccessHandler class:
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);
}
}
}
回答by oe.elvik
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;
}
}
回答by Mihael Mamula
If you want just different request body parser for login request just extend UsernamePasswordAuthenticationFilterand override attemptAuthenticationmethod.
By default UsernamePasswordAuthenticationFilterwill parse url encoded data and create UsernamePasswordAuthenticationTokenfrom it. Now you just need to make parser that will parse whatever you send to application.
如果您只需要用于登录请求的不同请求正文解析器,只需扩展UsernamePasswordAuthenticationFilter和覆盖attemptAuthentication方法。默认情况下UsernamePasswordAuthenticationFilter将解析 url 编码的数据并UsernamePasswordAuthenticationToken从中创建。现在您只需要制作解析器来解析您发送给应用程序的任何内容。
Here is example that will parse {"username": "someusername", "password": "somepassword"}
这是将解析的示例 {"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;
}
}
In snippet request body is extracted to string and mapped to object AuthReq(@Dataannotation is from lombok lib, it will generate seters and getters).
Than you can make UsernamePasswordAuthenticationTokenthat will be passed to default AuthenticationProvider.
在代码片段中,请求正文被提取为字符串并映射到对象AuthReq(@Data注释来自 lombok lib,它将生成 setter 和 getter)。比您可以UsernamePasswordAuthenticationToken将其传递给 default AuthenticationProvider。
Now you can extend WebSecurityConfigurerAdapterand override cnofigure method to replace old filter.
现在您可以扩展WebSecurityConfigurerAdapter和覆盖 cnofigure 方法来替换旧过滤器。
@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();
}
With addFilterAtmethod you replace default UsernamePasswordAuthenticationFilter. Dont forget to use @EnableWebSecurityannotation.
使用addFilterAt方法替换 default UsernamePasswordAuthenticationFilter。不要忘记使用@EnableWebSecurity注释。
回答by fl4l
Another way, according with thispost, is to manage manually the spring security authentication directly in the Controller.
In this manner is very simple to manage JSON input and avoid login redirect:
根据这篇文章,另一种方法是直接在控制器中手动管理 spring 安全身份验证。
以这种方式管理 JSON 输入并避免登录重定向非常简单:
@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;
}
回答by Intopass
Look at this example: https://github.com/fuhaiwei/springboot_security_restful_api
看这个例子: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);
}
}
回答by Martijn Dirkse
I applied the answers from fl4land oe.elvikfor login with JSON credentials in a Spring Boot application. I am working with annotation-based bean configuration.
我应用了fl4l和oe.elvik的答案,在 Spring Boot 应用程序中使用 JSON 凭据登录。我正在使用基于注释的 bean 配置。
In the referenced answers, a custom filter is created in which the authentication manager is injected. To do this, the authentication manager must be present as a Spring Bean. Here is a link on how to do that: https://stackoverflow.com/a/21639553/3950535.
在引用的答案中,创建了一个自定义过滤器,其中注入了身份验证管理器。为此,身份验证管理器必须作为 Spring Bean 存在。这是有关如何执行此操作的链接:https: //stackoverflow.com/a/21639553/3950535。
回答by Ali786
Here is the java configuration for the above solutions:
以下是上述解决方案的java配置:
@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();
}

