SpringSecurity简单入门

1. 概述

Spring Security是Spring家族中历史比较悠久的框架之一,具备完整而强大的功能体系。对于日常开发过程中常见的单体应用、微服务架构,以及响应式系统,Spring Security都能够进行无缝集成和整合,并提供多种常见的安全性功能。其核心主要包括:用户信息管理、敏感信息加解密、用户认证、权限控制、跨站点请求伪造保护、跨域支持、全局安全方法、单点登录等。

随着Spring Boot的兴起,开发人员可以零配置使用Spring Security。在Spring Boot应用程序中使用Spring Security,只须在Maven工程的pom文件中添加如下依赖即可。

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

在添加依赖后,spring security会自动为URL增加一个登录拦截。默认用户名是user,密码在springboot的启动日志里可以找到。
在这里插入图片描述

Spring Security中,安全配置通过继承WebSecurityConfigurerAdapter来配置。

@Configuration
public class MyWebSecurityConfigurerAdapter  extends WebSecurityConfigurerAdapter{
    protected void configure(HttpSecurity http) throws Exception {
    	//做大量的配置
    	//认证配置
		//授权配置
    }

2. 认证

认证 authentication ,解决你是谁的问题。

2.1 从数据库中查询登录

A、新建UserMapper以便于访问数据库,表结构如下
在这里插入图片描述

public interface UserMapper {
    User selectByUserName(String userName);

    List<String> selectAllRoleByUserId(Integer userId);

    List<String> selectPermissionsByUserId(Integer userId);

}

B、配置userMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.demo.mapper.UserMapper">
    <select id="selectByUserName" resultType="com.demo.entity.User">
        select id,userName,password
        from t_user where username = #{userName}
    </select>
    <select id="selectAllRoleByUserId" resultType="String">
        select r.name as name from t_role_user u,t_role r where r.id = u.rid and u.uid=#{userId};
    </select>
    <select id="selectPermissionsByUserId" resultType="String">
        SELECT permission FROM t_role r,t_role_user u,t_role_menu rm,t_menu m
        where r.id = u.uid and rm.mid = m.id and u.rid =rm.rid and u.uid=#{userId}
    </select>
</mapper>

C、新建一个类实现org.springframework.security.core.userdetails.UserDetailsService接口,这样就会调用该类的方法去访问数据库认证了。
这一步中会返回一个User对象,该对象是SpringSecurity里定义的User对象。
需要将用户的权限和角色用逗号分割后组装在一起。

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {

        com.demo.entity.User user = userMapper.selectByUserName(s);
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在");
        }

        List<String> roles = userMapper.selectAllRoleByUserId(user.getId());
        List<String> permissions = userMapper.selectPermissionsByUserId(user.getId());

        StringBuilder sb = new StringBuilder();
        for (String role : roles) {
            sb.append("ROLE_" + role + ",");
        }
        for (String permission : permissions) {
            sb.append(permission + ",");
        }

        String rolepermission = sb.substring(0, sb.length() - 1);

        UserDetails userDetails = new User(user.getUserName(), user.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList(rolepermission));
        return userDetails;
    }
}

D、注意,在上步骤中需要使用到一个passwordEncoder编码器,来生成加密密码明文生成密文。

    @Autowired
    private PasswordEncoder passwordEncoder;

通过java类来配置一个passwordEncoder

@Configuration
public class SecurityConfig {
    @Bean
    protected PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

2.2 认证配置

  • MyWebSecurityConfigurerAdapter中配置
  • 登录的页面/showLogin
  • 处理登录逻辑的入口/login
  • 登录成功后重定向的地址/showMain
  • 登录失败后重定向的地址/showFail
  • .usernameParameter("myusername")以及.passwordParameter("mypassword")定义了登录页面中的表单名称是myusernamemypassword
public class MyWebSecurityConfigurerAdapter  extends WebSecurityConfigurerAdapter{
    protected void configure(HttpSecurity http) throws Exception {
		
				http.formLogin()
                //未登录时的地址
                .loginPage("/showLogin")
                //处理登录请求的url
                .loginProcessingUrl("/login")
                //.successForwardUrl("/showMain")
                //认证成功后跳转的地址
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                        httpServletResponse.sendRedirect("/showMain");
                    }
                })
                //.failureForwardUrl("/showFail")
                //登录失败后的处理器
                .usernameParameter("myusername")
                //客户端的密码参数名称
                .passwordParameter("mypassword")
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
                        httpServletResponse.sendRedirect("/showFail");
                    }
                })
                ;
          }
}

2.3 异常配置

配置异常页面,如果程序异常测跳转到该页面。

        http.exceptionHandling()
                //.accessDeniedHandler(accessDeniedHandler);
                //只适用于非前端框架,适用于同步请求的方式
                //如果是异步请求需要使用上一种方式。
                .accessDeniedPage("/showAccessDenied");

如果是异步请求,如ajax,则可以实现AccessDeniedHandler 接口

@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        httpServletResponse.setStatus(httpServletResponse.SC_FORBIDDEN);
        PrintWriter writer = httpServletResponse.getWriter();
        writer.println("权限不足");
        writer.flush();
        writer.close();
    }
}

2.4 Remember Me

需要配置生成一个token,并将token下发给浏览器。
因此需要一个PersistentTokenRepository类。
通过java类的方式来定义bean,这个bean就是persistentTokenRepository

@Configuration
public class RememberMeConfig {
    @Autowired
    private DataSource dataSource;

    @Bean
    protected PersistentTokenRepository getPersistentTokenRepository(){
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        //jdbcTokenRepository.setCreateTableOnStartup(true);
        jdbcTokenRepository.setDataSource(dataSource);
        return jdbcTokenRepository;
    }
}

MyWebSecurityConfigurerAdapter中配置.rememberMe()

    private MyAccessDeniedHandler accessDeniedHandler;
    private PersistentTokenRepository persistentTokenRepository;
    protected void configure(HttpSecurity http) throws Exception {
        //其他配置
        http.rememberMe()
            .userDetailsService(userDetailsService)
            .tokenRepository(persistentTokenRepository)
            .tokenValiditySeconds(10*2);
    	//其他配置
    }

3. 授权

授权 authorization,解决你能做什么的问题。授权一般需要依托用户的角色,对权限集合进行绑定。

  • 因为必须先登录,因此对于登录页面antMatchers("/showLogin","/showFail").access("permitAll")也直接放行。
  • 因为静态资源要放行,所以对于.antMatchers("/images/**").permitAll() .regexMatchers("/js/.*").permitAll() .antMatchers("/demo").permitAll()这几个直接放行。
  • .antMatchers("/abc").denyAll() 所有到/abc的请求都拒绝。
  • 最后一行的.anyRequest().authenticated(); 声明所有的请求都需要登录。
public class MyWebSecurityConfigurerAdapter  extends WebSecurityConfigurerAdapter{
    protected void configure(HttpSecurity http) throws Exception {
				
				//前面的认证配置
				
				http.authorizeRequests()
                //.antMatchers("/showLogin","/showFail").permitAll()
                .antMatchers("/showLogin","/showFail").access("permitAll")
                //对于静态和动态请求需要分开
                //.antMatchers("/js/**").permitAll()
                .antMatchers("/abc").denyAll()
                .antMatchers("/jczl").hasAnyAuthority("demo:update")
                //.antMatchers("/jczl").hasAnyRole("ADMIN")
                .antMatchers("/admin").access("@myServiceImpl.hasPermission(request,authentication)")
                .antMatchers("/ip").hasIpAddress("192.168.7.86")
                .antMatchers("/images/**").permitAll()
                .regexMatchers("/js/.*").permitAll()
                .antMatchers("/demo").permitAll()
                .anyRequest().authenticated();

4. 安全csrf

为防止跨越的表单提交,在login.html 的隐藏域中需要加上

<input type="hidden" th:value="${_csrf.token}" name="_csrf" th:if="${_csrf}">

然后在MyWebSecurityConfigurerAdapter 中继续取消 http.csrf().disable();的注释,即默认开启csrf的校验了。

整体感受springsecurity的应用主要是要熟悉,并且跟随一起动手。springsecurity在微服务、OAuth2、响应式编程中都有大量的应用。在这里就不谈那么深了。
项目的的github地址是:https://github.com/forestnlp/springsecurity

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悟空学编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值