原创

Java架构师方案——Spring Security(一)快速入门(附完整项目代码)

1. 导读

1.读完这篇文章你将免费获得一个SpringSecurity测试demo项目源码(可直接运行)。
2.你将学到如何快速创建一个SpringSecurity项目,实现简单的登入功能,很方便的集成到Springboot项目。
3.查看demo项目的运行测试效果,更具体地了解SpringSecurity功能。。

2. 快速认识Spring Security

什么是Spring Security


Spring Security 的前身是 Acegi Security ,是 Spring 项目组中用来提供安全认证服务的框架。它支持众多的认证技术,如LDAP、基于IEFT RFC 的标准、Form-based authentication(简单用户接口)等等。概括就是:控制系统的登入拦截、用户登入认证、用户登入token的鉴权、系统api接口等资源的简单用户接口的权限控制。

简单地说,就是系统的登入逻辑和代码实现不用自己开发,使用Spring Security进行配置化开发就可以。

比较Spring Security登入业务与原生登入业务



原生登入逻辑开发流程有:

1)编写登入页面的form代码。

2)编写登入controller接口代码。

3)根据传入的用户名参数从数据库查询用户信息。

4)比较查询出来的密码数据与传入的密码,相同则登入通过,否则登入失败。

Spring Security登入逻辑开发流程:

1)编写登入页面的form代码。

2)Spring Security核心配置类开发。

3)实现UserDetailsService接口,在loadUserByUsername方法实现用户信息的加载。

为什么SpringSecurity登入业务开发工作量少



SpringSecurity开发工作少,是因为它对登入业务进行了非常完整的建模,登入业务的整个过程中,需要开发者自己开发的局部逻辑不多,其中包括:自定义登入页面,自定义用户数据获取来源(UserDetailsService实现类)

使用SpringSecurity来做系统的安全服务到底有多爽,下图代表了我此刻的内心。

alt


快速入门


SpringBoot项目pom依赖

这个是集成SpringSecurity所需要依赖的jar包。

<!--security start-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Springboot项目的pom文件中至少要包含上面两个依赖包。

SpringSecurity配置类

配置类要继承抽象类WebSecurityConfigurerAdapter,并使用注解@Configuration、@EnableGlobalMethodSecurity(prePostEnabled = true)修饰配置类。

/**
 * @Author: Galen
 * @Date: 2019/3/27-14:43
 * @Description: spring-security权限管理的核心配置
 **/
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserSecurityService userSecurityService;

    /**
     * @Author: jackdking
     * @Description: 配置userDetails的数据源,密码加密格式
     * @Date: 2020/5/21-5:24
     * @Param: [auth]
     * @return: void
     **/
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userSecurityService)
                .passwordEncoder(new BCryptPasswordEncoder());// 实现自定义登录校验
    }

    /**
     * @Author: Galen
     * @Description: 配置放行的资源
     * @Date: 2019/3/28-9:23
     * @Param: [web]
     * @return: void
     **/
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/login.html", "/index.html","/css/**", "/static/**", "/login_p", "/favicon.ico")
                // 给 swagger 放行;不需要权限能访问的资源
                .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/images/**", "/webjars/**", "/v2/api-docs", "/configuration/ui", "/configuration/security");
    }

    /**
     * @Author: Galen
     * @Description: 拦截配置
     * @Date: 2019/4/4-10:44
     * @Param: [http]
     * @return: void
     **/
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                // 使其支持跨域
                .requestMatchers(CorsUtils :: isPreFlightRequest).permitAll()
                // 其他路径需要授权访问
                .anyRequest().authenticated()

                .and()
                .formLogin().loginPage("/login_p")// 设置登录页面,
                //奇怪的是 这个url并没有被访问到。
                .loginProcessingUrl("/login")// 自定义的登录接口,这个接口必须和你登入页面form的action地址一样。否则一直进入login_p页面:如果loginProcessingUrl不配置,默认是跟loginPage一样
                .usernameParameter("username").passwordParameter("password")//告诉security form表单用户名和密码的参数表达式。
                .failureHandler(new MyAuthenticationFailureHandler())// 这个失败处理 跟 failureUrl 配置是互斥的。 两种只选择一种
                .successHandler(new MyAuthenticationSuccessHandler())//跟defaultSuccessUrl互斥 ,这个支持返回json数据。如果不设置这个成功后的处理器,则会报错 999。
//                .defaultSuccessUrl("/index")   //跟successHandler配置互斥,这个支持重定向到成功页面。 访问指定页面,用户未登入,跳转至登入页面,如果登入成功,跳转至用户访问指定页面,用户访问登入页面,默认的跳转页面
//                .failureUrl("/error_p") // 重定向失败页面   跟 failureHandler 配置是互斥的。 两种只选择一种
                .permitAll()

                .and()
                .csrf().disable(); //关闭csrf
    }
}
  • 配置类覆盖configure(AuthenticationManagerBuilder auth),设置用户信息来源接口userDetailsService的实现类,以及用户密码的加密实现对象BCryptPasswordEncoder。
  • 配置类覆盖configure(WebSecurity web),告知security框架哪些url不登入也能访问,例如一些css文件、js文件、登入页面等等。
  • 配置类覆盖configure(HttpSecurity http),告知security登入业务的一些信息,例如登录页面、自定义的登录接口、form表单用户名和密码的参数表达式、登入失败或成功的handler类。

userDetailsService的开发

在这个实现类中,security框架允许开发者自定义用户信息的来源:可以是在代码里面,或是数据库、缓存服务器、第三方服务等。在这个demo项目里,我们运用的比较简单,直接写死在代码中,创建了SecuritySysUser对象返回给Security。

SecuritySysUser securityUser = new SecuritySysUser();
securityUser.setUsername(username);
securityUser.setPassword("$2a$10$4H1JSQxyrJlguu0/V4DnR.s2NBjE.k6rI6.W.1AFL0UEnR2IR2/5y");
securityUser.setEnabled(true);

List<SecuritySysRole> roles = new ArrayList<>();
SecuritySysRole role = new SecuritySysRole();
role.setNameCn("ROLE_ADMIN");
securityUser.setRoles(roles);

if (securityUser == null) {
    throw new UsernameNotFoundException("用户名不对");
}

log.info("用户信息:{}" , securityUser.toString());

return securityUser;

到这,使用security框架来开发用户登入业务结束了,那我们来看看运行效果。

3. 运行Spring Security项目

本文的demo项目获取方式在文章底部查看。

右击项目文件com.jackdking.security.SpringSecurityApplication,选择Run As -> Java Application。

浏览器输入http://localhost:9090,进入login登入页面。

alt

输入用户名/密码:admin/admin,点击登入按钮,然后查看返回的登入json信息。

alt

登入成功返回的json数据是security框架的登入成功处理器返回的结果:successHandler(new MyAuthenticationSuccessHandler())。

@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
    response.setContentType("application/json;charset=utf-8");
    RespBean respBean = RespBean.ok("【MyAuthenticationSuccessHandler】登录成功!", SecurityUserUtil.getCurrentUser());
    new GalenWebMvcWrite().writeToWeb(request,response, respBean);
    System.out.println("【MyAuthenticationSuccessHandler】登录成功!");
}

输入错误的用户名和密码,返回的json信息,是错误处理器中的第一个异常错误类UsernameNotFoundException。

alt

登入失败返回的json数据是security框架的登入失败处理器返回的结果:failureHandler(new MyAuthenticationFailureHandler())。

 response.setContentType("application/json;charset=utf-8");
RespBean respBean;
if (exception instanceof BadCredentialsException ||
        exception instanceof UsernameNotFoundException) {
    respBean = RespBean.error("账户名或者密码输入错误!");
} else if (exception instanceof LockedException) {
    respBean = RespBean.error("账户被锁定,请联系管理员!");
} else if (exception instanceof CredentialsExpiredException) {
    respBean = RespBean.error("密码过期,请联系管理员!");
} else if (exception instanceof AccountExpiredException) {
    respBean = RespBean.error("账户过期,请联系管理员!");
} else if (exception instanceof DisabledException) {
    respBean = RespBean.error("账户被禁用,请联系管理员!");
} else {
    respBean = RespBean.error("登录失败!");
}

System.out.println("失败逻辑处理。");
//response.setStatus(401);
new GalenWebMvcWrite().writeToWeb(request, response, respBean);

4. 如何正确看待web安全服务

作为架构师,当然希望系统能使用成熟的、功能特性丰富的安全服务框架,而security正是做好的选择。而且无论是从建设安全服务的人力、测试、运维、风险等成本角度考虑,security都是非常香的。

Spring Security安全服务框架的配置化开发也大大提高了整个团队的开发效率,如果系统单独自己做一个安全服务框架,未来团队成员交接成本也会非常的高,也会伴随着较大的风险成本。

完整的demo项目,请关注公众号“前沿科技bot“并发送"SEC-ONE"获取。

alt

正文到此结束
本文目录