这里有新鲜出炉的精品教程, 程序狗速度看过来!
Spring Framework 开源 j2ee 框架
Spring 是什么呢? 首先它是一个开源的项目, 而且目前非常活跃; 它是一个基于 IOC 和 AOP 的构架多层 j2ee 系统的框架, 但它不强迫你必须在每一层 中必须使用 Spring, 因为它模块化的很好, 允许你根据自己的需要选择使用它的某一个模块; 它实现了很优雅的 MVC, 对不同的数据访问技术提供了统一的接口, 采用 IOC 使得可以很容易的实现 bean 的装配, 提供了简洁的 AOP 并据此实现 Transcation Managment, 等等
这篇文章主要介绍了详解 Spring Security 如何配置 JSON 登录, 小编觉得挺不错的, 现在分享给大家, 也给大家做个参考一起跟随小编过来看看吧
spring security 用了也有一段时间了, 弄过异步和多数据源登录, 也看过一点源码, 最近弄 rest, 然后顺便搭 oauth2, 前端用 json 来登录, 没想到 spring security 默认居然不能获取 request 中的 json 数据, 谷歌一波后只在 stackoverflow 找到一个回答比较靠谱, 还是得要重写 filter, 于是在这里填一波坑
准备工作
基本的 spring security 配置就不说了, 网上一堆例子, 只要弄到普通的表单登录和自定义 UserDetailsService 就可以因为需要重写 Filter, 所以需要对 spring security 的工作流程有一定的了解, 这里简单说一下 spring security 的原理
spring security 是基于 javax.servlet.Filter 的, 因此才能在 spring mvc(DispatcherServlet 基于 Servlet)前起作用
UsernamePasswordAuthenticationFilter: 实现 Filter 接口, 负责拦截登录处理的 url, 帐号和密码会在这里获取, 然后封装成 Authentication 交给 AuthenticationManager 进行认证工作
Authentication: 贯穿整个认证过程, 封装了认证的用户名, 密码和权限角色等信息, 接口有一个 boolean isAuthenticated()方法来决定该 Authentication 认证成功没;
AuthenticationManager: 认证管理器, 但本身并不做认证工作, 只是做个管理者的角色例如默认实现 ProviderManager 会持有一个 AuthenticationProvider 数组, 把认证工作交给这些 AuthenticationProvider, 直到有一个 AuthenticationProvider 完成了认证工作
AuthenticationProvider: 认证提供者, 默认实现, 也是最常使用的是 DaoAuthenticationProvider 我们在配置时一般重写一个 UserDetailsService 来从数据库获取正确的用户名密码, 其实就是配置了 DaoAuthenticationProvider 的 UserDetailsService 属性, DaoAuthenticationProvider 会做帐号和密码的比对, 如果正常就返回给 AuthenticationManager 一个验证成功的 Authentication
看 UsernamePasswordAuthenticationFilter 源码里的 obtainUsername 和 obtainPassword 方法只是简单地调用 request.getParameter 方法, 因此如果用 json 发送用户名和密码会导致 DaoAuthenticationProvider 检查密码时为空, 抛出 BadCredentialsException
- /**
- * Enables subclasses to override the composition of the password, such as by
- * including additional values and a separator.
- * <p>
- * This might be used for example if a postcode/zipcode was required in addition to
- * the password. A delimiter such as a pipe (|) should be used to separate the
- * password and extended value(s). The <code>AuthenticationDao</code> will need to
- * generate the expected password in a corresponding manner.
- * </p>
- *
- * @param request so that request attributes can be retrieved
- *
- * @return the password that will be presented in the <code>Authentication</code>
- * request token to the <code>AuthenticationManager</code>
- */
- protected String obtainPassword(HttpServletRequest request) {
- return request.getParameter(passwordParameter);
- }
- /**
- * Enables subclasses to override the composition of the username, such as by
- * including additional values and a separator.
- *
- * @param request so that request attributes can be retrieved
- *
- * @return the username that will be presented in the <code>Authentication</code>
- * request token to the <code>AuthenticationManager</code>
- */
- protected String obtainUsername(HttpServletRequest request) {
- return request.getParameter(usernameParameter);
- }
重写 UsernamePasswordAnthenticationFilter
上面 UsernamePasswordAnthenticationFilter 的 obtainUsername 和 obtainPassword 方法的注释已经说了, 可以让子类来自定义用户名和密码的获取工作但是我们不打算重写这两个方法, 而是重写它们的调用者 attemptAuthentication 方法, 因为 json 反序列化毕竟有一定消耗, 不会反序列化两次, 只需要在重写的 attemptAuthentication 方法中检查是否 json 登录, 然后直接反序列化返回 Authentication 对象即可这样我们没有破坏原有的获取流程, 还是可以重用父类原有的 attemptAuthentication 方法来处理表单登录
- /**
- * AuthenticationFilter that supports rest login(json login) and form login.
- * @author chenhuanming
- */
- public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
- @Override
- public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
- //attempt Authentication when Content-Type is json
- if(request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)
- ||request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)){
- //use jackson to deserialize json
- ObjectMapper mapper = new ObjectMapper();
- UsernamePasswordAuthenticationToken authRequest = null;
- try (InputStream is = request.getInputStream()){
- AuthenticationBean authenticationBean = mapper.readValue(is,AuthenticationBean.class);
- authRequest = new UsernamePasswordAuthenticationToken(
- authenticationBean.getUsername(), authenticationBean.getPassword());
- }catch (IOException e) {
- e.printStackTrace();
- new UsernamePasswordAuthenticationToken(
- "","");
- }finally {
- setDetails(request, authRequest);
- return this.getAuthenticationManager().authenticate(authRequest);
- }
- }
- //transmit it to UsernamePasswordAuthenticationFilter
- else {
- return super.attemptAuthentication(request, response);
- }
- }
- }
封装的 AuthenticationBean 类, 用了 lombok 简化代码(lombok 帮我们写 getter 和 setter 方法而已)
- @Getter
- @Setter
- public class AuthenticationBean {
- private String username;
- private String password;
- }
webSecurityConfigurerAdapter 配置
重写 Filter 不是问题, 主要是怎么把这个 Filter 加到 spring security 的众多 filter 里面
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http
- .cors().and()
- .antMatcher("/**").authorizeRequests()
- .antMatchers("/", "/login**").permitAll()
- .anyRequest().authenticated()
- // 这里必须要写 formLogin(), 不然原有的 UsernamePasswordAuthenticationFilter 不会出现, 也就无法配置我们重新的 UsernamePasswordAuthenticationFilter
- .and().formLogin().loginPage("/")
- .and().csrf().disable();
- // 用重写的 Filter 替换掉原有的 UsernamePasswordAuthenticationFilter
- http.addFilterAt(customAuthenticationFilter(),
- UsernamePasswordAuthenticationFilter.class);
- }
- // 注册自定义的 UsernamePasswordAuthenticationFilter
- @Bean
- CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
- CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
- filter.setAuthenticationSuccessHandler(new SuccessHandler());
- filter.setAuthenticationFailureHandler(new FailureHandler());
- filter.setFilterProcessesUrl("/login/self");
- // 这句很关键, 重用 WebSecurityConfigurerAdapter 配置的 AuthenticationManager, 不然要自己组装 AuthenticationManager
- filter.setAuthenticationManager(authenticationManagerBean());
- return filter;
- }
题外话, 如果搭自己的 oauth2 的 server, 需要让 spring security oauth2 共享同一个 AuthenticationManager(源码的解释是这样写可以暴露出这个 AuthenticationManager, 也就是注册到 spring ioc)
- @Override
- @Bean // share AuthenticationManager for web and oauth
- public AuthenticationManager authenticationManagerBean() throws Exception {
- return super.authenticationManagerBean();
- }
至此, spring security 就支持表单登录和异步 json 登录了
来源: http://www.phperz.com/article/18/0310/355857.html