SpringBoot Security快速上手 ajax 405解除拦截 源码+表结构

这篇文章源自我最近在做的一个需求,leader希望能做一个接口监控页,而这个页面需要做登录拦截。由于我比较懒,不想手写拦截器来做登录认证,而之前又没有玩过Spring Security,于是就把它引入进来了。

本文基于SpringBoot 2.2.6,希望这篇文章能够帮助你快速上手SpringBoot Security

增加pom依赖

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

增加此依赖后再启动项目,你应该能看到以下页面:
security登陆页面
如果你没做任何配置,默认的用户名是user,密码会打印在控制台上。你也可以在配置文件中增加以下配置用来显示的指定用户名和密码:

spring:
  security:
    user:
      name: user
      password: 123456

我们知道,我们平常写拦截器的时候都要手动地解除静态资源的拦截,SpringBoot Securiry也是如此,你需要增加以下配置来解除拦截:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
        web.ignoring()
                  .antMatchers("/static/**");
    }
}

至此,我以为一切都结束了,拦截器不用写了,登录过后页面之间都能正常跳转。正当我美滋滋地以为一切大功告成的时候发现:ajax请求不了了。无论我以什么姿势发送请求到后端,都会报405错误。
于是我上网去搜相关资料,发现原因是:Spring Security默认会对csrf做拦截,若想对ajax解除拦截,就得在配置项中禁用此项功能

实现授权逻辑

首先需要创建最基础的三张表:用户(user)角色(role)以及用户角色关联表(user_role),表结构如下:

user表

在这里插入图片描述

CREATE TABLE `ba_user` (
  `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `NAME` varchar(32) DEFAULT NULL COMMENT '用户名',
  `PASSWORD` varchar(255) DEFAULT NULL COMMENT '密码',
  `IS_LOCKED` tinyint(1) DEFAULT '0' COMMENT '是否有效',
  `IS_EXPIRED` tinyint(1) DEFAULT '0' COMMENT '是否过期',
  PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;

role表

在这里插入图片描述

CREATE TABLE `ba_role` (
  `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `ROLE_NAME` varchar(32) DEFAULT NULL COMMENT '角色名称',
  `IS_VALIDATE` tinyint(1) DEFAULT '0' COMMENT '是否有效',
  PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;

user_role表

在这里插入图片描述

CREATE TABLE `user_role` (
  `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `USER_ID` int(11) NOT NULL COMMENT '用户ID',
  `ROLE_ID` int(11) NOT NULL COMMENT '角色ID',
  PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;

实体类实现UserDetails接口

在实体类user中去实现一个接口UserDetails,这个接口是由Security提供的:

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "ba_user")
public class BaUser implements UserDetails {
    /**
     * 主键
     */
    @Id
    @Column(name = "ID")
    @GeneratedValue(generator = "JDBC")
    private Integer id;

    /**
     * 用户名
     */
    @Column(name = "`NAME`")
    private String name;

    /**
     * 密码
     */
    @Column(name = "`PASSWORD`")
    private String password;

    /**
     * 角色列表
     */
    private List<BaRole> roles;

    /**
     * 是否有效
     */
    @Column(name = "IS_LOCKED")
    private Boolean isLocked;

    /**
     * 是否过期
     */
    @Column(name = "IS_EXPIRED")
    private Boolean isExpired;

    public static BaUserBuilder builder() {
        return new BaUserBuilder();
    }
    /**
     * security底层会调用该方法来获取登录用户的角色信息
     * 实现逻辑根据自己的需求来写
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority>authorities=new ArrayList<>(roles.size());
        for (BaRole role : roles) {
            authorities.add(new SimpleGrantedAuthority(role.getRoleName()));
        }
        return authorities;
    }

    @Override
    public String getUsername() {
        return this.name;
    }

    @Override
    public boolean isAccountNonExpired() {
        return !this.isExpired;
    }

    @Override
    public boolean isAccountNonLocked() {
        return !this.isLocked;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return !this.isLocked;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

实现查询用户逻辑

Security底层帮我们做了用户认证,但是根据什么规则来进行用户查询的逻辑还是要自己实现的,比如我们需要根据用户名查询账号信息:
定义一个接口,该接口继承UserDetailsService,也是由Security提供的:

import org.springframework.security.core.userdetails.UserDetailsService;

public interface UserService extends UserDetailsService {
}

实现类:

@Service
@Transactional(readOnly = true)
@Slf4j
public class UserServiceImpl implements UserService {

    @Autowired
    private BaUserMapper userMapper;

    @Autowired
    private BaRoleMapper roleMapper;

    @Autowired
    private UserRoleMapper userRoleMapper;

    /**
     * 根据用户名查询用户信息
     * @param s 用户名
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        Condition condition = new Condition(BaUser.class);
        // 根据登录表单的用户名进行查询,并且账号的状态是有效的,我们一般定义0为"非" 1为"是"
        // 所以这段逻辑是查询"没被锁"的并且"没过期"的账号
        condition.createCriteria()
                .andEqualTo("name", s)
                .andEqualTo("isLocked", 0)
                .andEqualTo("isExpired", 0);
        List<BaUser> list = userMapper.selectByCondition(condition);
        if (list.size() <= 0) {
            throw new UsernameNotFoundException("用户不存在");
        }
        // 查询登录用户的角色
        BaUser user = list.get(0);
        Condition condition2 = new Condition(UserRole.class);
        condition2.createCriteria()
                .andEqualTo("userId", user.getId());
        // 拿到关联的角色列表
        List<UserRole> roles = userRoleMapper.selectByCondition(condition2);
        List<Integer> roleIds = Lists.newArrayList();
        if (roles.size() > 0) {
            roles.forEach( userRole -> {
                roleIds.add(userRole.getRoleId());
            });
        }
        // 获取角色名
        Condition condition3 = new Condition(BaRole.class);
        condition3.createCriteria()
                .andEqualTo("isValidate", 0)
                .andIn("id", roleIds);
        user.setRoles(roleMapper.selectByCondition(condition3));

        return user;
    }
}

配置SpringSecurity

之前我们在配置类中只配置了静态资源的放行,现在我们多做几个配置:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	// 将我们刚刚实现的service注入进来供security调用
    @Autowired
    private UserService userService;

    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService);
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
        web.ignoring()
                .antMatchers("/static/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
        		// 这句话代表登录过后就放行所有请求
                .anyRequest().authenticated()
                // 这句话代表表单登录以及登陆页面的路径,我用的是security自带的登录页
                .and().formLogin()
                .loginProcessingUrl("/login")
                // 这里的username和password要和登录表单的<name>属性一致,否则security找不到
                .usernameParameter("username").passwordParameter("password").permitAll()
                // 登出后的处理,security默认的登出路径是/logout 
                .and().logout().permitAll()
                // 关闭csrf ajax请求405的原因也在于此
                .and().csrf().disable();
    }
}

创建一个用户

user表中插入一条记录:
在这里插入图片描述
密码可以直接跑个单元测试生成:

@Test
    public void passTest() {
        String encode = new BCryptPasswordEncoder().encode("123456");
        System.out.println(encode);
    }

大功告成

如果配置到这里,你的程序应该就能够正常运行了。这里只给出了SpringBoot Security最基础的配置,目的是为了让程序快速跑起来看效果,之后复杂的配置再根据需求一点点的配就是了。比如网上很多配置都是长这样的:

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    UserService userService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(new PasswordEncoder() {
            @Override
            public String encode(CharSequence charSequence) {
                return DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
            }

            /**
             * @param charSequence 明文
             * @param s 密文
             * @return
             */
            @Override
            public boolean matches(CharSequence charSequence, String s) {
                return s.equals(DigestUtils.md5DigestAsHex(charSequence.toString().getBytes()));
            }
        });
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/**").hasRole("超级管理员")
                .anyRequest().authenticated()//其他的路径都是登录后即可访问
                .and().formLogin().loginPage("/login_page").successHandler(new AuthenticationSuccessHandler() {
            @Override
            public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                httpServletResponse.setContentType("application/json;charset=utf-8");
                PrintWriter out = httpServletResponse.getWriter();
                out.write("{\"status\":\"ok\",\"msg\":\"登录成功\"}");
                out.flush();
                out.close();
            }
        })
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
                        httpServletResponse.setContentType("application/json;charset=utf-8");
                        PrintWriter out = httpServletResponse.getWriter();
                        out.write("{\"status\":\"error\",\"msg\":\"登录失败\"}");
                        out.flush();
                        out.close();
                    }
                }).loginProcessingUrl("/login")
                .usernameParameter("username").passwordParameter("password").permitAll()
                .and().logout().permitAll().and().csrf().disable();
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/reg");
    }
}

上面的配置主要增加了权限的路径访问控制以及对登录成功登录失败之后要做的业务逻辑,这些逻辑对于我的项目来说暂不需要。

以最小知识原则去快速运用一项技术,要比一开始就把全部知识弄明白要更容易让人接受。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值