【SpringSecurity系列】SpringBoot集成SpringSecurity的表单登录

在如今的项目开发中,登录和权限管理是一个项目最基本的需求,对于这个需求SpringSecurity一个很好的架构,这一系列博文是我自己学习SpringSecurity的一些总结和自己踩过的坑和从零开始搭接一个完整的基于SpringSecurity的登录(包括第三方的登录)和权限管理的项目。

首先让我们来搭接一个springboot的项目,这里就不在多说,不会的可以自行百度,或者直接上gitHub上拉取我的这个范例git地址,首先我们来写一个controller用来测试使用:

@RestController
@RequestMapping("/user")
public class UserController {

    @GetMapping
    public List<User> query() {
        List<User> users = new ArrayList<User>();
        users.add(new User());
        users.add(new User());
        return users;
    }

}

使用SpringSecurity首先导入我们需要的jar包:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.social</groupId>
    <artifactId>spring-social-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.social</groupId>
    <artifactId>spring-social-config</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.social</groupId>
    <artifactId>spring-social-web</artifactId>
</dependency>

其实这样已经完成了一个springsecurity来管理我们的项目,当我们在浏览器上访问/user时,就会弹出SpringSecurity默认的表单登录页面,如图:

但是这个样子肯定不能满足我们的需求,所以我们需要对SpringSecurity进行一些配置,配置代码如下:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()//表示使用form表单提交
            .loginPage("/login.html")//我们定义的登录页
            .loginProcessingUrl("/authentication/form")//因为SpringSecurity默认是login请求为登录请求,所以需要配置自己的请求路径
            .and()
            .authorizeRequests()//对请求进行授权
            .antMatchers("/login.html").permitAll()//表示login.html路径不会被拦截
            .anyRequest()//表示所有请求
            .authenticated()//需要权限认证
            .and()
            .csrf().disable();//这是SpringSecurity的安全控制,我们这里先关掉
    }
}

然后就是我们简单的表单提交登录页面

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<h2>标准登录页面</h2>
<h3>表单登录</h3>
<form action="/authentication/form" method="post">
    <table>
        <tr>
            <td>用户名:</td>
            <td><input type="text" name="username"></td>
        </tr>
        <tr>
            <td>密码:</td>
            <td><input type="password" name="password"></td>
        </tr>
        <tr>
            <td colspan="2"><button type="submit">登录</button></td>
        </tr>
    </table>
</form>
</body>
</html>

当我们第一次访问/user时,就会跳到我们自定义的登录页面了

然后就需要对我们的登录的用户名和密码进行判断,先上代码:

@Component
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;


    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        String username = s;
        System.out.println("用户名为"+username);
        //根据username去数据库里查询获得密码
        String password = passwordEncoder.encode("123456");
        System.out.println("数据库密码为:"+password);
        return new User(username, passwordEncoder.encode("123456"),
                true,true,true,true,
                AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }
}

其中:PasswordEncoder是SpringSecurity提供给我们进行加密解密的,这里返回的User类不是我们自定义的,也是SpringSecurity提供好的,如果我们需要自定的话,可以让我们的类去集成UserDetails这个接口;看一下我们使用的这个User类的源码:

  public User(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
        if (username != null && !"".equals(username) && password != null) {
            this.username = username;
            this.password = password;
            this.enabled = enabled;
            this.accountNonExpired = accountNonExpired;
            this.credentialsNonExpired = credentialsNonExpired;
            this.accountNonLocked = accountNonLocked;
            this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
        } else {
            throw new IllegalArgumentException("Cannot pass null or empty values to constructor");
        }
    }

这个User类有4个判断,我们可以根据自己的设计进行逻辑判断是否满足条件。如果在启动项目的时候报PasswordEncoder 无法找到时,这个可以看一下我的另一篇博客,里面有解决方法解决PasswordEncoder无法注入问题。然后再次运行我们的项目:

再次登录,就可以登录成功,就可以进入我们的Controller层:

当我们重复登录两次时,看一下后台的输出的内容:

用户名为hudong
数据库密码为:$2a$10$laAqthwHpbTDMDxGkdPe9.zj.ARiAmhr7Lo0GDWXORPSdfafuPkwK
用户名为hudong
数据库密码为:$2a$10$fvmpjcWs9qDpFE7tgfK.O.FJCOky.H4EadfdXSOPmSPhT03YSCGpC

我们两次输入的密码是相同的,但后台打印出来的密文是不一样的,这就是PasswordEncoder的强大的地方,他通过加盐机制,可以让同一个密码返回不同的结果,有兴趣的小伙伴可以给我留言,人多的话,花时间给大家总结一下,这里就不详说了。

因为现在好多项目都是前后端分离的项目,我们只需要将登陆的成功后的数据发送给前端,而不需要我们来进行页面的跳转,那么如果来处理登录成功和失败以后的操作呢?

@Component("demoAuthenticationFailureHandler")
public class DemoAuthenticationFailureHandler implements AuthenticationFailureHandler {

    private Logger logger = LoggerFactory.getLogger(DemoAuthenticationFailureHandler.class);

    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 处理登录失败的请求
     * @author hdd
     * @date 2018/12/10 0010 10:15
     * @param [httpServletRequest, httpServletResponse, e-用来封装错误信息对象的] 
     * @return void
     */
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        logger.info("登录失败");
        httpServletResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        httpServletResponse.getWriter().write(objectMapper.writeValueAsString(e));
    }
}
@Component("demoAuthenticationSuccessHandler")
public class DemoAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    private Logger logger = LoggerFactory.getLogger(DemoAuthenticationSuccessHandler.class);

    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 处理登录成功的请求
     * @author hdd
     * @date 2018/12/10 0010 10:16
     * @param [httpServletRequest, httpServletResponse, authentication—封装登录信息的]
     * @return void
     */
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication)
            throws IOException, ServletException {
        logger.info("登录成功");

        httpServletResponse.setContentType("application/json;charset=UTF-8");
        httpServletResponse.getWriter().write(objectMapper.writeValueAsString(authentication));


    }
}

然后在我们的配合文件里,加入我们新的配置:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private DemoAuthenticationSuccessHandler demoAuthenticationSuccessHandler;

    @Autowired
    private DemoAuthenticationFailureHandler demoAuthenticationFailureHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()//表示使用form表单提交
            .loginPage("/login.html")//我们定义的登录页
            .loginProcessingUrl("/authentication/form")//因为SpringSecurity默认是login请求为登录请求,所以需要配置自己的请求路径
            .successHandler(demoAuthenticationSuccessHandler)//登录成功的操作
            .failureHandler(demoAuthenticationFailureHandler)//登录失败的操作
            .and()
            .authorizeRequests()//对请求进行授权
            .antMatchers("/login.html").permitAll()//表示login.html路径不会被拦截
            .anyRequest()//表示所有请求
            .authenticated()//需要权限认证
            .and()
            .csrf().disable();//这是SpringSecurity的安全控制,我们这里先关掉
    }
}

当我们重新进行登录的时候,看给我们返回的信息:

成功的时候:

失败的时候:

猜你喜欢

转载自blog.csdn.net/huxiaodong1994/article/details/84856989