Spring Security 初学

Spring Security 初学

声明:本篇文章无源码解析,属于初学范围,本文采用SpringBoot+thymeleaf的项目。

实现 SpringSecurity 分三步走

  • 继承 WebSecurityConfigurerAdapter 类
  • Override configure
  • configure(HttpSecurity http) (权限控制)
  • configure(AuthenticationManagerBuilder auth) (认证)
  • @EnableWebSecurity:开启WebSecurity模式

使用内存账号密码登录

环境准备

编写几个页面,首页用作跳转,跳转select、update、delete页面,编写对应controller,此时先别添加security的maven依赖

controller

MyController


@Controller
public class MyController {
<p>@GetMapping({"/","/index"})
public String toIndex(){
return "index";
}</p>
<p>@GetMapping("/select")
public String select(){
return "user/select";
}</p>
<p>@GetMapping("/delete")
public String delete(){
return "user/delete";
}</p>
<p>@GetMapping("/update")
public String update(){
return "user/update";
}</p>

}

index

index


    <title>Title</title>

<div>
    <a rel="noopener">select</a>
</div>

<div>
    <a rel="noopener">update</a>
</div>

<div>
    <a rel="noopener">delete</a>
</div>

目录结构如下

Spring Security 初学

启动项目试一下正常能否跳转。

使用内存账号密码来进行登录操作

添加 security maven依赖


    org.springframework.boot
    spring-boot-starter-security

  1. 继承 WebSecurityConfigurerAdapter 类,
  2. 重写俩个 configure方法
  3. @EnableWebSecurity 启用 WebSecurity
@EnableWebSecurity
public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

    //权限控制
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
    }

    //认证(登录认证)
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        super.configure(auth);
    }
}

此时直接启动项目,会发现需要登录,账号为 user,密码在启动项目中输出了

Using generated security password:

自动生成的密码肯定不符合我们正常需求,Spring Security提供了不使用数据库就可以配置账号密码的方法,修改认证方法即可。

在此之前,先配置访问 首页 、 select 、 delete 、update 的权限,修改 权限控制 的方法即可。

@Override
protected void configure(HttpSecurity http) throws Exception {
    //配置登录
    http.formLogin();
    //配置权限
    http.authorizeRequests()
        // 首页都可以访问
        .antMatchers("/","/index").permitAll()
        // 访问 /select 需要登录才能访问
        .antMatchers("/select").authenticated()
        // 访问 /delete 需要拥有 delete权限
        .antMatchers("/delete").hasAuthority("delete")
        // 访问 /update 需要拥有 role_update 角色权限
        .antMatchers("/update").hasRole("update");
}

配置用户账号密码及权限(暂时未使用数据库)

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    // 使用内存来设置账号密码
    auth.inMemoryAuthentication()
        // 密码加密工具,使用的是 security 中的
        .passwordEncoder(new BCryptPasswordEncoder())
        // 账号密码,密码使用的是加密后的密码,如果你知道加密后密码多少,也可以 .password("xx") //xx为加密后的密码
        .withUser("root").password(new BCryptPasswordEncoder().encode("1"))
        //给予权限,role其实也是Authority,角色就是权限前加一个 ROLE_
        .authorities("delete","ROLE_update")
        // and拼接多个用户
        .and()
        .withUser("test").password(new BCryptPasswordEncoder().encode("1"))
        .authorities("delete");
}

启动项目

Spring Security 初学

由于我们对三个页面都配置了权限,所以访问三个都需要登录操作,

对于select我们登录即可访问,delete需要delete权限,update需要update角色权限

我们登录root,三个都可以访问,登录test,只能访问select和delete,访问update会报403forbidden,

我们配置一下报错页面

新增一个error.html页面,配置对应的controller

@GetMapping("/error")
public String errorPage(){
    //跳转对应页面
    return "error";
}

修改 权限控制 方法,htpp是链式编程,可以接着上方formLogin或者添加角色的地方写,例如 http.formLogin().and().exceptionHandling().accessDeniedPage(“/error”);

@Override
protected void configure(HttpSecurity http) throws Exception {
    //配置登录
    http.formLogin();
    http.authorizeRequests()
            // 首页都可以访问
            .antMatchers("/","/index").permitAll()
            // 访问 /select 需要登录
            .antMatchers("/select").authenticated()
            // 访问 /delete 需要拥有 delete权限
            .antMatchers("/delete").hasAuthority("delete")
            // 访问 /update 需要拥有 role_update 角色权限
            .antMatchers("/update").hasRole("update");
    //新增错误页面跳转,跳转到接口
    http.exceptionHandling().accessDeniedPage("/error");
}

使用test账号登录,update页面就变成了配置的 error.html 页面

配置登录页面

使用Spring Security的登录页总归是不好的,自己写一个!

首先新建login.html页面

login.html


    <title>Title</title>

    <p>&#x7528;&#x6237;&#x540D;&#xFF1A;</p>
    <p>&#x5BC6;&#x7801;&#xFF1A;</p>

编写配置跳转的controller

@GetMapping("/toLogin")
public String toLogin(){
    return "login";
}

修改 权限控制 方法

@Override
protected void configure(HttpSecurity http) throws Exception {
    //配置登录
    http.formLogin()
            //登录页面跳转接口
            .loginPage("/toLogin")
            //登录页面提交时的方法名,注意是页面提交 action 的方法名
            .loginProcessingUrl("/login")
            //账号密码,登录页面 input 的 name 值,注意需要对应
            .usernameParameter("username").passwordParameter("pwd");

    http.authorizeRequests()
            // 首页都可以访问
            .antMatchers("/","/index").permitAll()
            // 访问 /select 需要登录
            .antMatchers("/select").authenticated()
            // 访问 /delete 需要拥有 delete权限
            .antMatchers("/delete").hasAuthority("delete")
            // 访问 /update 需要拥有 role_update 角色权限
            .antMatchers("/update").hasRole("update");
    http.exceptionHandling().accessDeniedPage("/error");
}

启动项目,成功跳转,成功登录

Spring Security 初学

可以在 index.html 页面加一个登录按钮,跳转至 /toLogin 方法。

登出(退出)

有登录按钮,就有退出按钮,刚才我还是删除浏览器记录才退出的,太麻烦了,写一个退出功能吧。

修改 权限控制 方法,如果为get请求的方法,需要关闭 csrf()

@Override
protected void configure(HttpSecurity http) throws Exception {

    //配置登录
    http.formLogin()
        //登录页面跳转接口
        .loginPage("/toLogin")
        //登录页面提交时的方法名,process,过程
        .loginProcessingUrl("/login")
        //账号密码,登录页面 input 的 name 值
        .usernameParameter("username").passwordParameter("pwd")
        .and()
        //退出操作
        .logout()
        //退出按钮的方法,如果前端退出按钮方法也为 logout,则此处可不写
        .logoutUrl("/logout")
        //退出成功跳转的方法
        .logoutSuccessUrl("/");

    //链式编程,可以直接接在上方
    //http.logout().logoutSuccessUrl("/");

    //如果不关闭,get请求会被拦截,需要验证,本例中的logout为get请求,需要关闭
    http.csrf().disable();

    http.authorizeRequests()
        // 首页都可以访问
        .antMatchers("/","/index").permitAll()
        // 访问 /select 需要登录
        .antMatchers("/select").authenticated()
        // 访问 /delete 需要拥有 delete权限
        .antMatchers("/delete").hasAuthority("delete")
        // 访问 /update 需要拥有 role_update 角色权限
        .antMatchers("/update").hasRole("update");
    http.exceptionHandling().accessDeniedPage("/error");
}

修改 index.html 页面,添加退出按钮

index.html


    <title>Title</title>

<a rel="noopener">&#x767B;&#x5F55;</a>
<a rel="noopener">&#x9000;&#x51FA;</a>
<div>
    <a rel="noopener">select</a>
</div>

<div>
    <a rel="noopener">update</a>
</div>

<div>
    <a rel="noopener">delete</a>
</div>

启动项目

Spring Security 初学

让页面退出和登录不同时存在

导入thymeleaf 和 springsecurity 的整合包(我的springboot版本2.5.2,低版本可能使用的是springsecurity4)


    org.thymeleaf.extras
    thymeleaf-extras-springsecurity5


    org.thymeleaf.extras
    thymeleaf-extras-springsecurity4
    3.0.2.RELEASE

html引入命名空间,注意 http://www.thymeleaf.org/thymeleaf-extras-springsecurity5http://www.thymeleaf.org/ + 导入依赖的名称,如果导入的是 springsecurity4 则为http://www.thymeleaf.org/thymeleaf-extras-springsecurity4

xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"

index.html


    <title>Title</title>

<div>
    <a rel="noopener">&#x767B;&#x5F55;</a>
</div>
<div>

    <p></p>
    <a rel="noopener">&#x9000;&#x51FA;</a>
</div>
<div>
    <a rel="noopener">select</a>
</div>

<div>
    <a rel="noopener">update</a>
</div>

<div>
    <a rel="noopener">delete</a>
</div>

启动项目

Spring Security 初学

一般来说,自己没有的权限,不应该显示出来,本例中 root 有所有权限,test 有 select、delete权限,我们让自己的权限显示出来,自己没有的权限不显示出来,修改 index.html 代码

index.html


    <title>Title</title>

<div>
    <a rel="noopener">&#x767B;&#x5F55;</a>
</div>
<div>

    <p></p>
    <a rel="noopener">&#x9000;&#x51FA;</a>
</div>
<div>
    <a rel="noopener">select</a>
</div>

<div>
    <a rel="noopener">update</a>
</div>

<div>
    <a rel="noopener">delete</a>
</div>

启动项目

Spring Security 初学

由于没有登录,所以什么权限都没有。

使用注解控制方法权限

修改 权限控制 方法,将其他需要登录验证的权限控制删除,添加 permit “/toLogin” 方法,其他方法都需要权限

@Override
protected void configure(HttpSecurity http) throws Exception {

    //配置登录
    http.formLogin()
        //登录页面跳转接口
        .loginPage("/toLogin")
        //登录页面提交时的方法名,process,过程
        .loginProcessingUrl("/login")
        //账号密码,登录页面 input 的 name 值
        .usernameParameter("username").passwordParameter("pwd")
        .and()
        //退出操作
        .logout()
        //退出按钮的方法,如果前端退出按钮方法也为 logout,则此处可不写
        .logoutUrl("/logout")
        //退出成功跳转的方法
        .logoutSuccessUrl("/");

    //        链式编程,可以直接接在上方
    //        http.logout().logoutSuccessUrl("/");

    //如果不关闭,get请求会被拦截,需要验证,本例中的logout为get请求,需要关闭
    http.csrf().disable();

    http.authorizeRequests()
        // 首页都可以访问
        .antMatchers("/","/index","/toLogin").permitAll()
        //                // 访问 /select 需要登录
        //                .antMatchers("/select").authenticated()
        //                // 访问 /delete 需要拥有 delete权限
        //                .antMatchers("/delete").hasAuthority("delete")
        //                // 访问 /update 需要拥有 role_update 角色权限
        //                .antMatchers("/update").hasRole("update");
        //其他方法都需要验证
        .anyRequest().authenticated();
    http.exceptionHandling().accessDeniedPage("/error");
}

修改对应controller增加与上方相同的权限

@Controller
public class MyController {

    @GetMapping({"/","/index"})
    public String toIndex(){
        return "index";
    }

    @GetMapping("/select")
    //需要认证,大部分与前端相同
    @PreAuthorize("isAuthenticated()")
    public String select(){
        return "user/select";
    }

    @GetMapping("/delete")
    //需要delete权限
    @PreAuthorize("hasAuthority('delete')")
    public String delete1(){
        return "user/delete";
    }

    @GetMapping("/update")
    //需要update权限
    @PreAuthorize("hasRole('update')")
    public String update(){
        return "user/update";
    }

    @GetMapping("/error")
    public String errorPage(){
        return "error";
    }

    @GetMapping("/toLogin")
    public String toLogin(){
        return "login";
    }

}

使用数据库账号密码登录

  • 实现 UserDetails 实现类
  • 实现 UserDetailsService 实现类
  • 修改 认证 方法

准备工作

mysql数据库

创建用户表及对应数据

CREATE TABLE user (
id int(20) primary key comment '用户id',
name varchar(30) comment '用户名',
pwd varchar(100) comment '密码'
);
insert into user values (1,'root','$2a$10$r.6c2l3FffSbTfHASQB.vepKYahB/Ct0VhvtgZESK3llquYlpM52q');
insert into user values (2,'test','$2a$10$r.6c2l3FffSbTfHASQB.vepKYahB/Ct0VhvtgZESK3llquYlpM52q');

创建用户权限表

CREATE TABLE user_auth (
id INT(20) comment '用户id',
auth VARCHAR(20) comment '权限'
);
insert into user_auth values (1,'delete');
insert into user_auth values (1,'ROLE_update');
insert into user_auth values (2,'delete');

实现 UserDetails 实现类

实现 UserDetails 接口即可,在重写方法中,他需要什么,我们写什么给他,比如需要password,给他写一个password,其他boolean值返回true即可。

编写无参和有参构造方法,给password和username赋值。

public class MyUserDetail implements UserDetails {

    private String password;
    private String username;
    Collection authorities;

    public MyUserDetail(){

    }

    public MyUserDetail(String password, String username) {
        this.password = password;
        this.username = username;
    }

    @Override
    public Collection getAuthorities() {
        return this.authorities;
    }

    @Override
    public String getPassword() {
        return this.password;
    }

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

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

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

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

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

实现 UserDetailsService 实现类

简单实现:查询用户,返回上方的实现类

@Service
public class MyUserServiceImpl implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //获取用户,根据用户名获取用户
        User user = userMapper.getUserByName(username);
        if(user == null){
            //用户不存在时抛出异常
            throw new UsernameNotFoundException("用户不存在");
        }
        //由于需要一个 UserDetails(interface),所以我们 new 一个我们写的实现类返回回去,
        MyUserDetail userDetail = new MyUserDetail(user.getPwd(), user.getName());
        return userDetail;
    }

}

修改 认证 方法

之前的认证是通过内存中的账号密码,本次使用数据库

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    //BCrypt加密每次盐都不一样,所以加密之后的值一般不一样,下面为 1 加密后的结果
    //$2a$10$r.6c2l3FffSbTfHASQB.vepKYahB/Ct0VhvtgZESK3llquYlpM52q
    auth.userDetailsService(userDetailsService)
        .passwordEncoder(new BCryptPasswordEncoder());
    //        // 使用内存来设置账号密码
    //        auth.inMemoryAuthentication()
    //                // 密码加密工具,使用的是 security 中的
    //                .passwordEncoder(new BCryptPasswordEncoder())
    //                // 账号密码,密码使用的是加密后的密码,如果你知道加密后密码多少,也可以 .password("xx") //xx为加密后的密码
    //                .withUser("root").password(new BCryptPasswordEncoder().encode("1"))
    //                //给予权限
    //                .authorities("delete","ROLE_update")
    //                // and拼接
    //                .and()
    //                .withUser("test").password(new BCryptPasswordEncoder().encode("1"))
    //                .authorities("delete");
}

启动项目

Spring Security 初学

配置权限

我们看到上方用户都只有select权限(select为登录就有权限),没有其他权限,所以我们需要添加用户相关权限。

在 UserDetailsService 实现类中,获取用户权限,set 进 UserDetails 中

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    //获取用户,根据用户名获取用户
    User user = userMapper.getUserByName(username);
    if(user == null){
        //用户不存在时抛出异常
        throw new UsernameNotFoundException("用户不存在");
    }
    //由于需要一个 UserDetails(interface),所以我们 new 一个我们写的实现类返回回去,
    MyUserDetail userDetail = new MyUserDetail(user.getPwd(), user.getName());

    //获取用户权限
    List auths = userMapper.getAuthById(user.getId());
    ArrayList authorityArrayList = new ArrayList<>();
    //配置权限
    for(String auth : auths){
        authorityArrayList.add(new SimpleGrantedAuthority(auth));
    }
    //如果没有set方法,需要到 MyUserDetail 中添加 set 方法
    /**
         *     public void setAuthorities(Collection authorities) {
         *         this.authorities = authorities;
         *     }
         */
    userDetail.setAuthorities(authorityArrayList);

    return userDetail;
}

重启验证

Spring Security 初学

注意事项

  • Role 和 Authority 是一个东西,role只是 Authority 前加一个 ROLE_
  • 退出使用 get 方法时需要关闭 csrf
  • 使用注解控制权限的时候需要添加 登录 方法可以访问
  • 使用的springboot版本
  • 如果想自定义加密方式实现 PasswordEncoder 接口即可
  • 由于html代码被转义了,附上代码地址

Original: https://www.cnblogs.com/ytryhard/p/14959024.html
Author: 抱糖果彡
Title: Spring Security 初学

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/571039/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • HM2022ssm-mp3【DQL查询编程控制】

    条件查询 1.1 条件查询的类 MyBatisPlus将书写复杂的SQL查询条件进行了封装,使用编程的形式完成查询条件的组合。 这个我们在前面都有见过,比如查询所有和分页查询的时候…

    Java 2023年6月5日
    084
  • Object类

    Java中存在最底层的类,所有类都直接或者间接的继承 java.lang.Object。如果在代码中,没有明确声明该类有继承的父类,那这个类的父类就是Object。 Object可…

    Java 2023年6月5日
    075
  • 使用ScheduledExecutorService线程池手动动态控制定时任务

    背景 在日常开发过程中,使用定时任务去执行一些业务逻辑是很常见的一种场景。比如定时发送短信,邮件,电商系统的定时自动收货、定时上下架功能等等。 一般实现定时任务有以下几种方案: J…

    Java 2023年5月30日
    063
  • UML的四种关系

    (1)泛化关系 (2)实现关系 (3)依赖关系 (4)关联关系 (5)聚合关系 (6)组合关系 (1)泛化关系 泛化(generalization)关系是一个类(称为子类、子接口)…

    Java 2023年6月5日
    089
  • Java中定义常量方法及建议(Class/Interface)

    采用”类.常量名”方法进行调用。需要私有化构造方法,避免创建该类的实例。同时不需让其他类继承该类。 如果多处需要访问工具类中定义的常量,可以通过静态导入(s…

    Java 2023年5月29日
    065
  • Ceph集群搭建记录

    环境准备 基础环境 node00 192.168.247.144 node00 node01 192.168.247.135 node01 node02 192.168.247.1…

    Java 2023年6月10日
    088
  • 练习阅读新闻

    从小到大对新闻不敏感,直到现在复试面试时,老师可能会问到最近的一些热点新闻,才开始关注。可以这么说,我对于世界的认识,还停留在高中的历史课……可见我多么迂腐…

    Java 2023年6月5日
    072
  • OptaPlanner的新约束表达方式 Constraint Streams

    有好些时间没有写过关于OptaPlanner的东西了,其实近半年来,OptaPlanner还是推出了不少有用、好用的新特性。包括本文讲到的以Stream接口实现评分编程。关于Opt…

    Java 2023年6月16日
    087
  • 基于LSM的Key-Value数据库实现WAL篇

    上篇文章简单的实现了基于LSM数据库的初步版本,在该版本中如数据写入到内存表后但还未持久化到SSTable排序字符串表,此时正好程序崩溃,内存表中暂未持久化的数据将会丢失。 引入W…

    Java 2023年6月16日
    077
  • 深度剖析Istio共享代理新模式Ambient Mesh

    摘要:今年9月份,Istio社区宣布Ambient Mesh开源,由此引发国内外众多开发者的热烈讨论。 今年9月份,Istio社区宣布Ambient Mesh开源,由此引发国内外众…

    Java 2023年6月15日
    070
  • mybatis 新增返回主键

    java;gutter:true; insert into t_ob_recycle_record (belong_project,task_name,recycle_time,r…

    Java 2023年5月30日
    062
  • 2021网络协议从入门到底层原理2《小码哥》

    应用层的常见协议 2021网络协议从入门到底层原理2 1、超文本传输:HTTP、HTTPS2、 文件传输:FTP3、电子邮件:SMTP、POP3、IMAP4、 动态主机配置:DHC…

    Java 2023年6月7日
    077
  • [解决]程序包org.springframework.boot不存在

    解决程序包org.springframework.boot不存在 一、问题 编译工程,提示「程序包org.springframework.boot不存在」、但是maven本地仓库是…

    Java 2023年5月29日
    096
  • 设计模式之策略模式

    策略模式属于行为型模式,是使用最多的设计模式之一;其作用是针对一组算法,将每一个算法封装到具体共同接口的独立的类种,从而使得他们可以相互转化。策略模式使得算法可以在不影响到客户端得…

    Java 2023年6月5日
    079
  • HDU 1711 Number Sequence (KMP)

    白书说这个是MP,没有对f 数组优化过,所以说KMP有点不准确 #include int a,b; int T[1000010],P[10010];//从0开始存 int f[10…

    Java 2023年5月29日
    050
  • Java8 日期时间API

    一、转换 1、与字符串 //LocalDateTime 转 字符串 String str = DateTimeFormatter.ofPattern("yyyy-MM-d…

    Java 2023年6月5日
    077
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球