零基础快速入门SpringSecurity+SpringBoot

引言

我先说明一下,我写这篇文章的原因:因为毕业设计的后台需要使用到Spring Security,但是之前从来没有使用过Security,就直接百度Security如何整合Spring Boot。期间查阅了不下10篇文章,自己编写代码了不下3次,都没有成功,一直卡在读取数据库那里。这些文章基本上全都是代码,看得云里雾里的,基本上没有注释,让没有使用过Spring Security的人如何下手呢。(也有可能是我太菜了,确实。。。)
如果你也是没有使用过Spring Security的话,那么这篇文章将十分适合你!!
代码我已经提交到了gitee上了,一边看着文章一边看着总的代码更有助于理解。
代码的类、变量的命名确实大学问,我是随便命名的,大家不要学我。。。
gitee地址:https://gitee.com/zh_727729853/spring-boot–spring-security

SrpingSecurity简介

SpringSecurity基于Spring框架,提供了一套Web应用安全性的完整解决方案。

主要包括了2个重要区域:“认证” 和 “授权”。
(1)认证:判断某个用户是否能登录。
(2)授权:判断某个用户是否有权限去做某些事情。
优点:

  1. 和Spring无缝结合
  2. 全面的权限控制
  3. 专门为Web开发sheji

缺点:

  1. 重量级

Shiro

优点:

  • 轻量级

缺点:

  • 有些特定需求需要手动编写代码

快速入门

创建一个SpringBoot的项目,导入Web和Security的依赖

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

这样就已经集成了Security了,我们编写一个Controller测试一下

@RestController
@RequestMapping("/test")
public class TestController {
    @GetMapping("/security")
    public String test1(){
        return "Hello,Security";
    }
}

当我们在浏览器输入这个请求地址的时候,默认会跳转到Security的登录界面,提示需要我们登录
在这里插入图片描述
默认的账号是:user 密码会在你IDEA的输出控制台上在这里插入图片描述
当我们成功输入了用户名、密码后,就会正常显示网页的内容了。在这里插入图片描述
OK,快速入门就这么简单得结束了!

UserDetailsService接口

当我们什么都没有配置的时候,默认使用的是Security默认的账号和随机生成的密码。我们实际开发中,登录的账号和密码应该从数据库中查询。所以我们要通过自定义逻辑控制认证逻辑。

我们编写一个类实现这个接口,然后重写loadUserByUsername(),在里面实现通过用户名查询数据库,得到这个用户的所有信息,将它返回给Security进行处理。

UserDeatilsService接口:查询数据库用户名、密码。

创建类继承UsernamePasswordAuthenticationFilter,重写3个方法。
创建类实现UserDetailsService接口,编写查询数据库过程,返回User对象。这个User对象是Security提供的对象。

PasswordEncoder接口

主要是用来对密码加密。用于返回User对象的密码加密。

我们一般使用里面的BCrypt。

设置用户名密码的方式

一共有3种方式:

  1. 通过配置文件(application.yml/properties)
  2. 通过配置类
  3. 自定义编写实现类

SpringSecurity查找用户名密码的顺序:

  • 首先会在你的配置文件或者配置类中查找,有的话直接使用你配置好的。如果没有才会到你自定义编写的实现类中查找。(上面的三种方式按照1.2.3按顺序执行的)

方式一(通过配置文件):

spring.security.user.name=zh
spring.security.user.password=727729853

方式二:通过配置类

首先,创建一个config包,在config包中创建SecurityConfig类,并且继承WebSecurityConfigurerAdapter。

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    public PasswordEncoder passwordEncoder(){
        //密码加密  使用的是PasswordEncoder中的BCrypt加密
        return new BCryptPasswordEncoder();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //密码需要解码,调用encode()
        //roles是给这个用户设置的权限(现在还用不上)
   auth.inMemoryAuthentication().withUser("zh").password(passwordEncoder().encode("123456")).roles("admin");
    }
}

如果配置成功,启动的话,IDEA的控台不会再给你输出它设置好的随机密码
在这里插入图片描述
登录localhost:8080/test/security
然后用自己设置好的账号密码登录
在这里插入图片描述
方式三:

注意:
	要先注释掉application.yml/properties的账号密码     
	和SecurityConfig类的configure(AuthenticationManagerBuilder auth) 中的	 	 auth.inMemoryAuthentication().withUser("zh").password(passwordEncoder().encode("123456")).roles("admin");
	(就是之前方式一和方式二的账号密码)

开始,我是在Service包中创建一个MyUserService类,实现了UserDetailsService接口

//需要设置bean的名称,否则到时候注入的时候,因为有2个UserDetailsService类型的bean,Spring不知道注入哪个。
@Service("userDetailsService")
public class MyUserService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        //我们先写死数据 后面再通过数据库进行查询
        //通过Security自带的工具类AuthorityUtils获取权限
        //这里只用知道大概意思就好了,会用就OK了
        List<GrantedAuthority> authorityList= AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
        //这里的User是Security自带的
        //里面的参数是  用户名,密码,权限
        return new User("zh",new BCryptPasswordEncoder().encode("111111"),authorityList);
    }
}

这是Security自带的User对象的参数(源码)
在这里插入图片描述
修改一下SecurityConfig类

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    //注意这里,注入的是UserDetailsService,不是我们创建的类MyUserService
    @Autowired
    private UserDetailsService userDetailsService;
    @Bean
    public PasswordEncoder passwordEncoder(){
        //密码加密  使用的是PasswordEncoder中的BCrypt加密
        return new BCryptPasswordEncoder();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //设置获取账号密码的方式是通过自定义编写类
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }
}

然后我们像之前访问localhost:8080/test/security 然后登录就OJBK了。

用数据库进行认证授权

因为需要连接数据库,所以必定要准备要依赖,我这里使用的是mybatis-plus,可以简化mybatis开发

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.0.5</version>
        </dependency>
                <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
 		<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

在数据库中建立3张表:
1、用户表 account
2、角色表 role
3、权限表 perssion

1、account
在这里插入图片描述
2、 role
在这里插入图片描述
3、perssion
在这里插入图片描述

pojo 的名称为Account(尽量别用User,因为使用User命名的类太多了,容易导出包)

@AllArgsConstructor
@NoArgsConstructor
@Data
public class Account {
    private String id;
    private String username;
    private String password;
    private boolean deleteFlag;
    private int roleId;
}

dao 的名称为AccountMapper
不会Mybatis-plus的就自己写SQL语句(Select * from 表名 where username=#{username})

@Repository
public interface AccountMapper extends BaseMapper<Account> {
}

修改MyUserService类


/**
 * 主要作用是接收前端传过来的用户名
 * 然后自己写业务代码从数据库中查询
 */
//需要设置bean的名称
@Service("userDetailsService")
public class MyUserService implements UserDetailsService {
    @Autowired
    private AccountMapper accountMapper;
    @Autowired
    private PerssionMapper perssionMapper;
    @Autowoired
    private RoleMapper roleMapper;
    //方法的形参名称修改一下  改成username
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //mybatis-plus  等价于(Select * from 表名 where username=#{username})
        QueryWrapper<Account> queryWrapper = new QueryWrapper();
        queryWrapper.eq("username",username);
        Account account = accountMapper.selectOne(queryWrapper);
        if (account==null){
            throw new RuntimeException("没有这个用户");
        }
        List<GrantedAuthority> authorityList = null;
        if (StringUtils.isNotBlank(users.getRoleId())) {
            Role role = roleMapper.findRoleById(users.getRoleId());
            if (role != null) {
                // 获取角色名称
                authorityList = AuthorityUtils.commaSeparatedStringToAuthorityList(role.getRoleName());
                // 获取角色权限
                Set<String> set = perssionMapper.getPerssionById(role.getId());
                if (!set.isEmpty()) {
                    for (String s : set) {
                        authorityList.add(new SimpleGrantedAuthority(s));
                    }
                }
            }
            //将查询到的相关信息返回给Security
            return new User(users.getUsername(), users.getPassword(), authorityList);
        }
        return null;
    }
}

自定义登录页面

先配置一下SecurityConfig类


@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    //注意这里,注入的是UserDetailsService,不是我们创建的类MyUserService
    @Autowired
    private UserDetailsService userDetailsService;
    @Bean
    public PasswordEncoder passwordEncoder(){
        //密码加密  使用的是PasswordEncoder中的BCrypt加密
        return new BCryptPasswordEncoder();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //设置获取账号密码的方式是通过自定义编写类
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .loginPage("/login.html")    //登录的页面     注意:这里有个"/"
                .loginProcessingUrl("/user/login")  
            //登录提交请求的url    我们不需要在controller中编写这个接口,页面提交的时候Security后帮我们拦截然后去MyUserService中的loadUserByUsername()去数据库中查找
                .defaultSuccessUrl("/test/security").permitAll() //登录成功后默认跳转的页面
                .and().authorizeRequests().antMatchers("/test/hello","/user/login").permitAll()  
      //哪些url路径可以直接访问,不需要登录 注意需要放行css、js等资源
                .anyRequest().authenticated()      //所有请求都可以访问?
                .and().csrf().disable();    //关闭csrf
    }
}

在Controller中添加一个路径

@RestController
@RequestMapping("/test")
public class TestController {
    //测试中,这个接口需要登录才能访问
    @GetMapping("/security")
    public String test1(){
        return "Hello,Security";
    }
    //可以直接访问
    @GetMapping("/hello")
    public String hello(){
        return "hello";
    }
}

登录页面 login.html 在resources/static目录下

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/html">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/user/login" method="post">
        用户名:<input name="username" type="text"><br/>
        密的码:<input name="password" type="password">
        <input type="submit">
    </form>
</body>
</html>

然后就可以进行相关测试了

权限认证

介绍一下方法:

  • hasAuthority() 方法里面只能写一个权限角色 只有符合这个权限角色的才能访问

  • hasAnyAuthority() 方法里面可以设置多个权限角色,只要符合其中之一的都可以访问

  • hasRole() 方法里的角色,底层会自动帮你和ROLE_拼接。(例如:方法里的参数是admin,对应需要的权限角色就是ROLE_admin)

    hasAnyRole() 也是同理

当没有权限访问的时候,页面会报403错误。

SecurityConfig


@Configuration
@EnableGlobalMethodSecurity(prePostEnabled=true)        // 开启后,可以通过注解的形式标识接口有哪些角色或者权限可以访问
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    //注意这里,注入的是UserDetailsService,不是我们创建的类MyUserService
    @Autowired
    private UserDetailsService userDetailsService;
    @Bean
    public PasswordEncoder passwordEncoder(){
        //密码加密  使用的是PasswordEncoder中的BCrypt加密
        return new BCryptPasswordEncoder();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //设置获取账号密码的方式是通过自定义编写类
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .loginPage("/login.html")    //登录的页面     注意:这里有个"/"
                .loginProcessingUrl("/user/login")
                //登录提交请求的url    我们不需要在controller中编写这个接口,页面提交的时候Security后帮我们拦截然后去MyUserService中的loadUserByUsername()去数据库中查找
                .defaultSuccessUrl("/test/security").permitAll() //登录成功后默认跳转的页面
                .and().authorizeRequests().antMatchers("/test/hello","/user/login").permitAll()  //哪些url路径可以直接访问,不需要登录
                .antMatchers("/test/authen").hasAuthority("admin")  //只有admin才能访问
                .antMatchers("/test/any").hasAnyAuthority("admin,student")  //只有admin或者student权限才能访问
                .anyRequest().authenticated()      //所有请求都可以访问?
                .and().csrf().disable();    //关闭csrf
    }
}

TestController新增两个接口

    //只有admin才能访问的方法  配置要看SecurityConfig
    @GetMapping("/authen")
    public String authen(){
        return "authen";
    }
    //admin、student都能访问的方法
    @GetMapping("/any")
    public String any(){
        return "any";
    }

自定义403页面

我们新建一个html页面(my403.html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>没有权限!!!</h1>
</body>
</html>

修改SecurityConfig类的配置 主要加了这一行代码 http.exceptionHandling().accessDeniedPage("/my403.html");


@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    //注意这里,注入的是UserDetailsService,不是我们创建的类MyUserService
    @Autowired
    private UserDetailsService userDetailsService;
    @Bean
    public PasswordEncoder passwordEncoder(){
        //密码加密  使用的是PasswordEncoder中的BCrypt加密
        return new BCryptPasswordEncoder();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //设置获取账号密码的方式是通过自定义编写类
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .loginPage("/login.html")    //登录的页面     注意:这里有个"/"
                .loginProcessingUrl("/user/login")
                //登录提交请求的url    我们不需要在controller中编写这个接口,页面提交的时候Security后帮我们拦截然后去MyUserService中的loadUserByUsername()去数据库中查找
                .defaultSuccessUrl("/test/security").permitAll() //登录成功后默认跳转的页面
                .and().authorizeRequests().antMatchers("/test/hello","/user/login").permitAll()  //哪些url路径可以直接访问,不需要登录
                .antMatchers("/test/authen").hasAuthority("admin")  //只有admin才能访问
                .antMatchers("/test/any").hasAnyAuthority("admin,student")  //只有admin或者student权限才能访问
                .anyRequest().authenticated()      //所有请求都可以访问?
                .and().csrf().disable();    //关闭csrf
        //没有权限的时候,跳转到我们自定义的403页面
        http.exceptionHandling().accessDeniedPage("/my403.html");
    }
}

我们再修改一下MyUserService中,我们自己写死的权限,随便修改成别的就好了!!

登录测试
在这里插入图片描述

注解快速开发

说明:三个注解都要开启注解支持 (大概这个意思吧~~)

  • @Secured() 作用于方法之上,表名拥有哪些权限角色才能访问该方法。

注意:@Secured ()里的角色要以ROLE_ 开头,登录用户的角色权限也要以ROLE_ 开头。

  • @PreAuthorize() 在执行方法之前进行权限校验。

使用方法:@PreAuthorize(“hasAnyAuthority(‘角色权限’)”) 注意符号的使用 角色权限直接填写 不需要加ROLE_。

  • @PostAuthorize() 执行完方法之后进行权限校验,失败则向用户返回权限不足,但是里面的方法还是执行了。(使用的不多)

使用方法:@PostAuthorize(“hasAnyAuthority(‘角色权限’)”) 注意符号的使用 角色权限直接填写 不需要加ROLE_。

@Secured使用

首先,要开始Security的注解扫描 @EnableGlobalMethodSecurity(securedEnabled = true) 可以放在Config类上也可以是启动类上

@SpringBootApplication
@MapperScan("com.zhong.dao")
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
//开启@Secured支持securedEnabled  开始@PreAuthorize和@PostAuthorize支持 都是prePostEnabled
public class SpringBootAndSecurity {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootAndSecurity.class,args);
    }
}

新建一个Test2Controller 这个类里所有的接口都是使用注解来标识权限的。


@RestController
@RequestMapping("/test2")
public class Test2Controller {
    @GetMapping("/and")
    @Secured({"ROLE_admin","ROLE_admin1"})		//必须是以ROLE_开头  用户的角色也要是以ROLE_开头
    public String test1(){
        return "使用注解进行权限校验成功";
    }
}

@PreAuthorize使用

在Test2Controller中添加接口

    //执行方法之前进行校验
    @GetMapping("/before")
    @PreAuthorize("hasAnyAuthority('student')")
    public String before(){
        return "before";
    }

测试。。。。

@PostAuthorize使用
在Test2Controller中添加接口

 //执行完方法之后进行权限校验,失败则向用户返回权限不足,但是里面的方法还是执行了。
    @GetMapping("/after")
    @PostAuthorize("hasAnyAuthority('student')")
    public String after(){
        return "after";
    }

用户注销功能

编写一个Handler

@Component
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.sendRedirect("/login.html");
    }
}

修改WebSecurityConfig类

 @Resource
 private MyLogoutSuccessHandler myLogoutSuccessHandler;
//  省略很多代码
 @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                //自定义登录的页面
                .loginPage("/login.html")
                //登录的请求url地址
                .loginProcessingUrl("/user/login")
                //登录成功后跳转的默认地址
                .defaultSuccessUrl("/admin/test").permitAll()
                .and().authorizeRequests()
                //放行的资源(不需要登陆)
                .antMatchers
                        ("/admin/login", "/", "/css/**", "/fonts/**", "/js/**", "/img/**", "/plugins/**", "/index/**", "/order/**", "/catrgory/**", "/goods/**", "/test/**", "/paysuccess.html").permitAll()
                .anyRequest().authenticated()
                // 用户退出   新增一行代码
                .and().logout().logoutSuccessHandler(myLogoutSuccessHandler)
                .and().csrf().disable();    // 自定义成功页面的时候,要使用csrf().disable() 关闭这个过滤器

    }

最后编写一个退出按钮,请求路径是localhost:端口/logout即可

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值