SpringSecurity整合SpringBoot全干货使用教程

本文没有任何原理解释,没有任何使用讲解,而且记录的是比较基本的操作,不适合初学者入门,适合已学过后的方法笔记以便于下次快速上手使用~
如有错误,请指出,感谢观看!

一.环境介绍

本文采用环境为SpringBoot2.1.3,并且使用了Thymeleaf模板引擎(暂未使用前后端分离,以后会写),目前暂时不使用数据库连接,所有需要数据库模拟操作的地方,我会指出。

目录介绍和需求介绍:

在这里插入图片描述
测试环境暂时只是一个TestController,所有请页面均写在此处,TestSecurityConfig为SpringSecurity的配置类。
在这里插入图片描述

目前暂时使用三个页面,test1和test2页面分别代替真实环境中不同的业务页面(比如:学生管理,作业管理等),需要我们权限控制访问。

访问成功后会有如下提示:
在这里插入图片描述

二.使用SpringSecurity

下面让我们用简单的模拟来熟悉SpringSecurity的基本使用吧!

1.引入SpringSecurity

我们想要使用SpringSecurity,只需要在pom文件中添加入如下依赖即可:

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

添加后,重启项目,就可以看到SpringSecurity已经生效(以前不需要登陆的页面需要登陆才可使用):

在这里插入图片描述
登陆的用户名为user,密码会在我们控制台输出:
在这里插入图片描述
输入即可登录系统!

2.HttpBasic登录模式

如果我们不需要单独的登录页面,安全性的需求不是很高,我们可以让SpringSecurity使用最基本的HttpBasic登录模式!

使用此模式,我们需要在TestSecurityConfig配置类中重写WebSecurityConfigurerAdapter的配置,代码如下:

@Configuration
public class TestSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable() //关闭csrf)跨站请求伪造攻击防御(开启后可能会拦截测试请求,导致登录失败)
                .httpBasic()
                .and()
                    .authorizeRequests()//定义哪一些请求需要被权限认证保护
                    .anyRequest()//所有请求都会被保护
                        .authenticated();//登陆后才可以访问
    }
}

在这里插入图片描述
配置后,我们直接使用简单的登陆弹框,验证后即可访问页面!

3.FormLogin登录模式与自定义登录页

比起HttpBasic登录模式,FormLogin更适合我们的业务需求,SpringSecurity默认为FormLogin登录模式!

Formlogin可以设置自定义登录页,代码如下:

@Configuration
public class TestSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable() //关闭csrf)跨站请求伪造攻击防御(开启后可能会拦截测试请求,导致登录失败)
                .formLogin()//formLogin登录模式
                    .loginPage("/login")//设置登录页,未登录访问会自动跳转到该登录页
                    .usernameParameter("username")//设置登录页提交的用户名的字段名称
                    .passwordParameter("password")//设置登录页提交密码的字段名称
                    .defaultSuccessUrl("/index")//默认登陆成功之后跳转的页面
                    .failureForwardUrl("/login")//失败后跳转的页面
                .and()
                    .authorizeRequests()//定义哪一些请求需要被权限认证保护
                        .antMatchers("/login")//登录页设置访问权限
                            .permitAll()//允许所有用户访问
                        .anyRequest()//所有请求都会被保护
                            .authenticated();//登陆后才可以访问
    }
}

注意:

  • 和以往不同的是,SpringSecurity的登陆不需要自己手写登陆的逻辑,只需要编写登录页即可,SpringSecurity通过自己的拦截器,拦截发送的/login的post请求,所以我们只需要向/login发送登陆请求即可!
  • 不管是通过表单,还是通过ajax,都可以向/login发送请求进行登录,但需要注意后端绑定的字段需要与前端发送的字段相同,否则SpringSecurity无法正确获取登录信息。
    在这里插入图片描述

在此处测试我们就可以发现,未登录跳转到登录页,成功失败后均跳转到相应页面!

在这里插入图片描述

注意:

 .defaultSuccessUrl("/index")//默认登陆成功之后跳转的页面

我们可以使defaultSuccessUrl方法设置登陆成功后跳转的url,但我们测试会发现,如果我们直接访问其它页面被拦截(比如http://localhost:8080/test2)的话,登录成功后会直接跳转到之前访问的页面(直接跳转到/test2对应的页面),如果我们需要登录成功后跳转到指定页面,则可以适用如下方法:

.successForwardUrl("/index")//登陆成功后访问的页面(无论之前访问什么页面,都跳转到此页面)

注意:
如果以上测试不生效,则有可能是权限控制代码顺序的问题:
在这里插入图片描述
注意代码顺序一定不能颠倒,上方设置完成后下方再设置就会不生效,如果把绿框代码放到上方,就会导致登录页也会被拦截,所以编写此代码时,一定要把大范围的授权放在下方,而具体授权规则的代码放在上方!

4.从数据库获取用户信息(模拟)

我们实际的使用中都是通过RBAC权限将用户的所有信息查询出来,但本文章并不会实际查询,只是模拟查询,如果文章由看不懂的地方,可以去完整学习SpringSecurity,本文只是学习后的记录,以便于下次快速使用,并不是完整教程,抱歉~

4.1.密码加密操作和BCryptPasswordEncoder类

我们的数据库存储的密码一定不能是明文,为了避免出现安全问题,我们可以使用BCryptPasswordEncoder类将密码加密后存储到数据库中。

示例:将字符串“admin”加密

        String password = new BCryptPasswordEncoder().encode("admin");
        System.err.println(password);

结果
在这里插入图片描述
我们可以直接在添加用户时,将用户设置的密码直接加密为此格式,使用数据库存储即可!

4.2.自定义用户登录逻辑

我们想要自定义登录逻辑,首先需要实现UserDetailsService接口,接口内规定了实现自定义登录逻辑的方法!

在这里插入图片描述

之后在配置类中将此接口设置为自定义登录逻辑(注意:因为之前使用了密码加密,所以也要讲密码加密方式一起设置,否则框架无法判断加密后的密码是否正确):

    @Autowired
    private MyUserDetailService myUserDetailService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(myUserDetailService).passwordEncoder(new BCryptPasswordEncoder());
    }

此时我们就可以使用自定义密码输入逻辑,再接口方法中,我们会接受到前段输入的用户名信息,我们可以在此方法中查询数据库,然后将查询出的信息写入UserDetails类中返回即可!(注意:因为之前配置了密码加密类,所以我们并不需要在查询出密码后手动解密,只需要将数据库中查询出来的密码的密文返回即可,框架会自动解密并判断前端输入的密码是否正确):

@Component
@Slf4j
public class MyUserDetailService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        //先在这里模拟数据库操作,假设这些是数据库出来的
        MyUserDetails userDetails = new MyUserDetails();
        userDetails.setUsername(userName);//登录的用户名
        //此处应将从数据库查询出来的加密后的密码取出(因为没有数据库存,所以直接模拟一下)
        String password = new BCryptPasswordEncoder().encode("admin");
        userDetails.setPassword(password);//登陆的密码
        userDetails.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList("menu1"));//用户具有的权限
        return userDetails;//将UserDetails对象返回,框架会自定分析密码是否正确,是否具有权限,无需手动操作
    }
}

其中:UserDetails为接口,里面规定了框架获取用户信息的方式,简单实现即可

public class MyUserDetails implements UserDetails {

    private String username;//用户名
    private String password;//密码
    private boolean accountNonExpired;//是否没过期
    private boolean accountNonLocked;//是否锁定
    private boolean credentialsNonExpired;//是否没过期
    private boolean enabled;//是否可用
    private Collection<? extends GrantedAuthority> authorities;//权限集合
    @Override
    public Collection<? extends GrantedAuthority> 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;
    }

    /*自己定义的构造方法用于赋值*/

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public void setAccountNonExpired(boolean accountNonExpired) {
        this.accountNonExpired = accountNonExpired;
    }

    public void setAccountNonLocked(boolean accountNonLocked) {
        this.accountNonLocked = accountNonLocked;
    }

    public void setCredentialsNonExpired(boolean credentialsNonExpired) {
        this.credentialsNonExpired = credentialsNonExpired;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
        this.authorities = authorities;
    }
}

SpringSecurity框架主要分为认证和授权两大操作,认证即登录,目前认证逻辑全部完成!

5.资源权限设置与用户的授权

在SpringSecurity中,我么可以为每一个url,方法等设置访问权限,用户登录时,通过数据库查询出用户具有哪些权限,如果没有相应权限,禁止访问,有权限则放行访问!

5.1.UserDetails用户授权

在这里插入图片描述

在我们之前实现过的UserDetails类中,有一个权限即可,我们要为用户添加权限,直接将从数据库查询出来的权限即可添加即可!

userDetails.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList("menu1,menu2,test1,test2"));

假设我们从数据库中查询到当前登录用户有如上四个权限,只需将其写成逗号间隔的形式,设置即可!

5.2.为资源设置访问权限——url方式设置

在这里插入图片描述
在我们之前的配置中,这样的方式就是为url设置权限(上方代码就是为“/login”url设置了任意用户均可访问的权限)
在这里插入图片描述
结合之前的测试页面,我们为“test1”设置“test1:check”权限,为“test2”设置“test2:check”权限!

@Configuration
public class TestSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable() //关闭csrf)跨站请求伪造攻击防御(开启后可能会拦截测试请求,导致登录失败)
                .formLogin()//formLogin登录模式
                    .loginPage("/login")//设置登录页,未登录访问会自动跳转到该登录页
                        .usernameParameter("username")//设置登录页提交的用户名的字段名称
                        .passwordParameter("password")//设置登录页提交密码的字段名称
                        .defaultSuccessUrl("/index")//默认登陆成功之后跳转的页面
                        .failureForwardUrl("/login")//失败后跳转的页面
                .and()
                    .authorizeRequests()//定义哪一些请求需要被权限认证保护
                        .antMatchers("/login")//登录页设置访问权限
                            .permitAll()//允许所有用户访问
                        .antMatchers("/test1")
                            .hasAnyAuthority("test1:check")
                        .antMatchers("/test2")
                            .hasAnyAuthority("test2:check")
                         .anyRequest()//所有请求都会被保护
                            .authenticated();//登陆后才可以访问
    }
}

这样,如果我们为用户只设置了test1:check权限,该用户就可以访问/test1页面,但访问/test2权限时就会发生异常,因为该用户并没有访问此url的权限!

5.3.为资源设置访问权限——方法设置权限

除了为URL设置权限外,我们还可以为方法设置权限!
首先,我们需要在配置类上加入以下注解:

@EnableGlobalMethodSecurity(prePostEnabled = true)//开启方法级别的权限验证

在这里插入图片描述

开启方法级别的权限认证后,我们就可以通过注解为方法加入权限:

 @PreAuthorize("权限认证符")

例如:为“test1”设置“test1:check”权限,为“test2”设置“test2:check”权限

@GetMapping("/test1")
@PreAuthorize("hasAnyAuthority('test1')")
public String test1()
{
    return "test1";
}

@GetMapping("/test2")
@PreAuthorize("hasAnyAuthority('test2')")
public String test2()
{
    return "test2";
}

次方法设置权限与上方url设置权限效果基本相同,并且功能更加强大,除了在方法执行前验证,还可以在方法执行后验证返回值等,与url设置相同,同样可以为一个方法设置多个权限!

@PreAuthorize("hasAnyAuthority('test2') or hasAnyAuthority('menu2')")//只要具有其中任意权限即可访问

注意:此方式并不仅限于Controller验证,在Service,Dao中仍然可以使用,但建议控制Controller方法!

6.自定义登陆成功与失败页面

登录成功页面:
继承SavedRequestAwareAuthenticationSuccessHandler类并重写onAuthenticationSuccess方法即可!
登录失败页面:
继承SimpleUrlAuthenticationFailureHandler类并重写onAuthenticationFailure方法即可!

重写完成后需在配置类中进行如下配置:

  .failureHandler(myFaildHandler)//设置自定义逻辑处理登录失败
  .successHandler(mySuccessHandler)//设置自定义逻辑处理登录成功

完整配置:

  @Configuration
    public class TestSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MySuccessHandler mySuccessHandler;

    @Autowired
    private MyFaildHandler myFaildHandler;

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable() //关闭csrf)跨站请求伪造攻击防御(开启后可能会拦截测试请求,导致登录失败)
                    .formLogin()//formLogin登录模式
                        .loginPage("/login")//设置登录页,未登录访问会自动跳转到该登录页
                            .usernameParameter("username")//设置登录页提交的用户名的字段名称
                            .passwordParameter("password")//设置登录页提交密码的字段名称
                            .failureHandler(myFaildHandler)//设置自定义逻辑处理登录失败
                            .successHandler(mySuccessHandler)//设置自定义逻辑处理登录成功
                    .and()
                        .authorizeRequests()//定义哪一些请求需要被权限认证保护
                            .antMatchers("/login")//登录页设置访问权限
                                .permitAll()//允许所有用户访问
                            .antMatchers("/test1")
                                .hasAnyAuthority("test1:check")
                            .antMatchers("/test2")
                                .hasAnyAuthority("test2:check")
                             .anyRequest()//所有请求都会被保护
                                .authenticated();//登陆后才可以访问
        }
  		@Override
	    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
	        auth.userDetailsService(myUserDetailService).passwordEncoder(new BCryptPasswordEncoder());
    	}
    }

登陆成功处理代码:

@Component
public class MySuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
        //在这里实现自定义逻辑
    }
}

登录失败处理代码:

@Component
public class MyFaildHandler extends SimpleUrlAuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
   		//在这里实现自定义逻辑
    }
}

其中:登陆成功逻辑处还可获取一个Authentication对象

public interface Authentication extends Principal, Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();//权限信息列表,GrantedAuthority类,通常是代表权限信息的一系列字符串。

    Object getCredentials();//密码信息,用户输入的密码字符串,在认证过后通常会被移除。

    Object getDetails();//WebAuthenticationDetails类,包括用户的ip地址和sessionId的值等。

    Object getPrincipal();//大部分情况下返回的是UserDetails接口的实现类

    boolean isAuthenticated();

    void setAuthenticated(boolean var1) throws IllegalArgumentException;
}

所以,我们就可以通过此对象获取登录用户的详细信息:

 		UserDetails userDetails = (UserDetails) authentication.getPrincipal();
        System.err.println(userDetails.getUsername());
        System.err.println(userDetails.getPassword());

7.单点登录

在SpringSecurity中想要实现单点登录,只需要设置允许最大Session个数即可,并且将可重新登陆的选项打开,这样,当新用户登陆后,之前登录的用户会自动失效!

  .and()
      .sessionManagement()//管理Session
      .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)//在必要时创建session
      .sessionFixation()//重新登陆后session策略
      	.migrateSession()//复制Session信息到新的session
      .maximumSessions(1)//同一用户允许最大Session个数
      .maxSessionsPreventsLogin(false)//已登录用户是否允许重新登陆

自定义session失效逻辑

当session失效后,我们仍可以自定义session失效逻辑,只需要实现SessionInformationExpiredStrategy接口即可!

@Component
public class MySessionStrategyHandler implements SessionInformationExpiredStrategy {
    @Override
    public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
        event.getResponse().getWriter().println("session过期,请重新登陆");//可以通过event得到resopnse和resquest对象
    }
}

逻辑定义完成后,在配置类中定义即可:

.expiredSessionStrategy(mySessionStrategyHandler)//自定义session失效逻辑

8.自定义退出逻辑

要想自定义退出逻辑,我们只需要在配置类中配置即可:

   .and()
      .logout()//logout配置
          .logoutUrl("/logout")//自定义退出url
          .logoutSuccessUrl("/login");//退出成功后跳转的页面

在这里插入图片描述

上方代码,我们将退出url设置为“/logout”,这样我们只需要访问此链接,就会自动退出登录!
退出后,当前用户对应的session信息会被清除,这时即使跳转回之前的页面,也需要重新登陆才可访问!

如果要自定义复杂退出逻辑,实现LogoutSuccessHandler实现其中方法即可!

@Component
public class MyLoguotHandler implements LogoutSuccessHandler {

    @Override
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        //写一些自定义的业务逻辑
    }
}
   .and()
      .logout()//logout配置
          .logoutUrl("/logout")//自定义退出url
          .logoutSuccessHandler(myLoguotHandler);//自定义的退出逻辑!

9.记住登陆

如果我们需要长时间免登录直接可访问功能,我们就可以使用“记住我”功能!

开启记住我功能很简单,只需要在配置类中开启即可:

 .and()
    .rememberMe();//开启记住我功能

然后在前端页面加入选项即可!
在这里插入图片描述

如果我们想控制记住我的时间,在配置类中自定义接口(默认为两周):

.and()
    .rememberMe()
       .tokenValiditySeconds(60 * 60 * 24 * 7);//设置记住我时间

如果要更改前端传入的字段名称,也可以修改:

.and()
      .rememberMe()
            .tokenValiditySeconds(60 * 60 * 24 * 7)//设置记住我时间
             .rememberMeParameter("remember-me");//设置记住我的字段

无论设置为什么字段,只需要在登录时的post表单中一同提交即可,非常方便~

感谢大家查看我的文章,接下来还会有更多SpringSecurity的内容~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值