SpringSecurity

目录

一、UserDetailsService

二、PasswordEncoder

三、自定义登录逻辑

1、SecurityConfig

2、UserDetailsService实现类

3、权限的定义AuthorityUtils.commaSeparatedStringToAuthorityList("admin, normal")

4、自定义登录页面

5、失败跳转

6、设置请求账户和密码的参数名

7、自定义登录成功处理器

8、自定义登录失败处理器

四、授权认证方法详解

1.anyRequest

​2.antMatchers

3.regexMatchers

4.mvcMatchers

五、内置访问控制方法

六、角色权限判断

hasAuthority(String)

hasAnyAuthority(String ...)

hasRole(String)

hasAnyRole(String ...)

hasIpAddress(String)

IP地址判断

七、自定义403处理方案

新建类实现 AccessDeniedHandler

八、基于表达式的访问控制

 access()方法使用

使用自定义方法

九、基于注解的访问控制

@Secured

开启注解

@PreAuthorize 和@PostAuthorize 都是方法或类级别注解

开启注解

十、RememberMe功能实现

十一、Thymeleaf中SpringSecurity的使用

获取属性

根据源码得出下面属性:

新建demo.html->编写Controller

权限判断

设置用户角色和权限

控制页面显示效果:在页面中根据用户权限和角色判断页面中显示的内容

退出登录:用户只需要向 Spring Security 项目中发送/logout退出请求即可。实现退出非常简单,只要在页面中添加/logout 的超链接即可。

为了实现更好的效果,通常添加退出的配置。默认的退出 url 为/logout,退出成功后跳转到/login?logout。如果不希望使用默认值,可以通过下面的方法进行修改。

logout其他常用配置源码解读


一、UserDetailsService

当什么也没有配置的时候,账号和密码是由 Spring Security 定义生成的。而在实际项目中账号和密码都是从数据库中查询出来的。所以我们要通过自定义逻辑控制认证逻辑。如果需要自定义逻辑时,只需要实现 UserDetailsService 接口即可。

返回值 UserDetails 是一个接口。

二、PasswordEncoder

  • ​encode():把参数按照特定的解析规则进行解析。
  • matches() :验证从存储中获取的编码密码与编码后提交的原始密码是否匹配。如果密码匹配,则返回 true;如果不匹配,则返回 false。第一个参数表示需要被解析的密码。第二个参数表示存储的密码。
  • upgradeEncoding():如果解析的密码能够再次进行解析且达到更安全的结果则返回 true,否则返回 false。默认返回 false。
@Test
    public void contextLoads() {
        //如果是实现自定义登录逻辑,容器里要有PasswordEncoder实例(不能new),所以要写配置类进行注入
        PasswordEncoder pe = new BCryptPasswordEncoder ();
        String encode = pe.encode("123");
        System.out.println(encode);
        boolean matches = pe.matches("123", encode);
        System.out.println("------------------");
        System.out.println(matches);

    }

三、自定义登录逻辑

1、SecurityConfig

2、UserDetailsService实现类

3、权限的定义AuthorityUtils.commaSeparatedStringToAuthorityList("admin, normal")

4、自定义登录页面

SecurityConfig extends WebSecurityConfigurerAdapter 重写configure方法

//表单提交

http.formLogin()

               //自定义登录页面

               .loginPage("/login.html");

发现页面都可以访问,之前的验证好像失效了。故进行授权操作(类似于拦截器):

@Override
    protected void configure(HttpSecurity http) throws Exception {
        //表单提交
        http.formLogin()
                //自定义登录页面
                .loginPage("/login.html");
        //授权认证
        http.authorizeRequests()
                //login.html不需要被认证
                .antMatchers("/login.html").permitAll()
                //所有请求都必须被认证,必须登录之后被访问
                .anyRequest().authenticated();
    }

可以跳转到login.html,但是没有执行到loadUserByUsername。故要改配置,让它走登录逻辑。

//表单提交
        http.formLogin()
                //当发现/login认为是登录,必须和表单提交的地址一样,去执行UserDetailServiceImpl
                .loginProcessingUrl("/login")   //对应login.html里action
                //自定义登录页面
                .loginPage("/login.html");

这时发现还是不行,因为需要关闭csrf防护。

//关闭csrf防护
        http.csrf().disable();

成功打印UserDetailServiceImpl.loadUserByUsername,说明loadUserByUsername成功执行。添加跳转successForwardUrl

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //表单提交
        http.formLogin()
                //当发现/login认为是登录,必须和表单提交的地址一样,去执行UserDetailServiceImpl
                .loginProcessingUrl("/login")   //对应login.html里action
                //自定义登录页面
                .loginPage("/login.html")
                //登录成功后跳转页面,post请求
                .successForwardUrl("/toMain");

        //授权认证
        http.authorizeRequests()
                //login.html不需要被认证
                .antMatchers("/login.html").permitAll()
                //所有请求都必须被认证,必须登录之后被访问
                .anyRequest().authenticated();

        //关闭csrf防护
        http.csrf().disable();

    }

    @Bean
    public PasswordEncoder getPw(){
        return new BCryptPasswordEncoder();
    }
    
}

5、失败跳转

  1. 准备error.html页面以及controller
  2. http.formLogin().failureForwardUrl("/toError");
  3. http.authorizeRequests().antMatchers("/error.html").permitAll()

6、设置请求账户和密码的参数名

默认通过的UsernamePasswordAuthenticationFilter过滤的。

如果需要自定义,在配置类中配置

7、自定义登录成功处理器

之前是通过post请求controller做页面跳转,前后端分离基本不用controller做页面跳转。

自定义handler:MyAuthenticationSuccessHandler

public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    private String url;
    public MyAuthenticationSuccessHandler(String url) {
        this.url = url;
    }
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {
        response.sendRedirect(url);
    }
}

修改配置:

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {
        User user = (User) authentication.getPrincipal();
        System.out.println(user.getUsername());
        //输出null
        System.out.println(user.getPassword());
        System.out.println(user.getAuthorities());
        response.sendRedirect(url);
    }

执行结果:

8、自定义登录失败处理器

public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
    private String url;

    public MyAuthenticationFailureHandler(String url) {
        this.url = url;
    }
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.sendRedirect(url);
    }
}

四、授权认证方法详解

1.anyRequest

在之前认证过程中我们就已经使用过 anyRequest(),表示匹配所有的请求。一般情况下此方法都会使用,设置全部内容都需要进行认证。

    //授权认证
    http.authorizeRequests()
                //error.html不需要被认证
                .antMatchers("/error.html").permitAll()
                //login.html不需要被认证
                .antMatchers("/login.html").permitAll()
                //所有请求都必须被认证,必须登录之后被访问
                .anyRequest().authenticated();

​2.antMatchers

参数是不定向参数,每个参数是一个 ant 表达式,用于匹配 URL规则。

规则如下:

?: 匹配一个字符

*:匹配 0 个或多个字符

** :匹配 0 个或多个目录

在实际项目中经常需要放行所有静态资源,下面演示放行 js 文件夹下所有脚本文件。

.antMatchers("/js/**","/css/**").permitAll()

还有一种配置方式是只要是.js 文件都放行

.antMatchers("/**/*.js").permitAll()

3.regexMatchers

介绍:使用正则表达式进行匹配。和 antMatchers()主要的区别就是参数,antMatchers()参数是 ant 表达式,regexMatchers()参数是正则表达式。

 演示所有以.js 结尾的文件都被放行:

.regexMatchers( ".+[.]js").permitAll()

两个参数时使用方式

无论是antMatchers()还是regexMatchers()都具有两个参数的方法,其中第一个参数都是 HttpMethod,表示请求方式,当设置了HttpMethod后表示只有设定的特定的请求方式才执行对应的权限设置。

枚举类型 HttpMethod内置属性如下:

4.mvcMatchers

mvcMatchers()适用于配置了 servletPath 的情况。

servletPath就是所有的 URL 的统一前缀。在 SpringBoot 整合SpringMVC 的项目中可以在 application.properties 中添加下面内容设置 ServletPath

spring.mvc.servlet.path=/xxx

在 Spring Security 的配置类中配置.servletPath()是 mvcMatchers()返回值特有的方法,antMatchers()和 regexMatchers()没有这个方法。在servletPath()中配置了servletPath后,mvcMatchers()直接写 SpringMVC 中@RequestMapping()中设置的路径即可。

如果不习惯使用 mvcMatchers()也可以使用 antMatchers(),下面代码和上面代码是等效

五、内置访问控制方法

  1. permitAll()表示所匹配的 URL 任何人都允许访问。
  2. authenticated()表示所匹配的 URL 都需要被认证才能访问。
  3. anonymous()表示可以匿名访问匹配的URL。

和permitAll()效果类似,只是设置为 anonymous()的 url 会执行 filter 链中。

  1. denyAll()表示所匹配的 URL 都不允许被访问。
  2. rememberMe():被“remember me”的用户允许访问
  3. fullyAuthenticated():如果用户不是被 remember me 的,才可以访问。

六、角色权限判断

除了之前讲解的内置权限控制。Spring Security 中还支持很多其他权限控制。这些方法一般都用于用户已经被认证后,判断用户是否具有特定的要求。

hasAuthority(String)

判断用户是否具有特定的权限(例如会员系统),用户的权限是在自定义登录逻辑中创建 User 对象时指定的。下图中 admin和normal 就是用户的权限。admin和normal 严格区分大小写。

在配置类中通过 hasAuthority(“admin”)设置具有 admin 权限时才能访问。

hasAnyAuthority(String ...)

如果用户具备给定权限中某一个,就允许访问。 下面代码中由于大小写和用户的权限不相同,所以用户无权访问。

hasRole(String)

如果用户具备给定角色就允许访问。否则出现 403。

参数取值来源于自定义登录逻辑 UserDetailsService实现类中创建 User 对象时给 User 赋予的授权。在给用户赋予角色时角色需要以:ROLE_开头,后面添加角色名称。例如:ROLE_abc 其中 abc 是角色名,ROLE_是固定的字符开头。 使用 hasRole()时参数也只写 abc 即可。否则启动报错。

给用户赋予角色:

hasAnyRole(String ...)

如果用户具备给定角色的任意一个,就允许被访问。

hasIpAddress(String)

如果请求是指定的 IP 就运行访问。 可以通过 request.getRemoteAddr()获取 ip 地址。 需要注意的是在本机进行测试时 localhost 和 127.0.0.1 输出的 ip地址是不一样的。

当浏览器中通过 localhost 进行访问时控制台打印的内容:

当浏览器中通过 127.0.0.1 访问时控制台打印的内容:

当浏览器中通过具体 ip 进行访问时控制台打印内容:

IP地址判断

七、自定义403处理方案

新建类实现 AccessDeniedHandler

@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
                       AccessDeniedException accessDeniedException) throws IOException, ServletException {
        //设置响应状态码
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);   //403
        response.setHeader("Content-Type", "application/json;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.write("{\"status\":\"error\",\"msg\":\"权限不足,请联系管理员\"}");
        writer.flush();
        writer.close();
    }
}

提示信息:

八、基于表达式的访问控制

 access()方法使用

之前学习的登录用户权限判断实际上底层实现都是调用access(表达式),可以通过access()实现和之前学习的权限控制完成相同的功能。

以 hasRole 和 和 permitAll 举例:

使用自定义方法

虽然这里面已经包含了很多的表达式(方法)但是在实际项目中很有可能出现需要自己自定义逻辑的情况。

判断登录用户是否具有访问当前 URL 权限。

新建接口及实现类

@Service
public class MyServiceImpl implements MyService{

    @Override
    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
        Object o = authentication.getPrincipal();
        if(o instanceof UserDetails){
            UserDetails userDetails = (UserDetails) o;
            Collection extends GrantedAuthority> authorities = userDetails.getAuthorities();
            return authorities.contains(new SimpleGrantedAuthority(request.getRequestURI()));
        }
        return false;
    }
}

修改配置类

UserDetailServiceImpl权限增加/main.html

故可以正常访问到main.html

九、基于注解的访问控制

@Secured

@Secured 是专门用于判断是否具有角色的。能写在方法或类上。参数要以 ROLE_开头。

开启注解

在 启 动 类 ( 也 可 以 在 配 置 类 等 能 够 扫 描 的 类 上 ) 上 添 加 @EnableGlobalMethodSecurity(securedEnabled = true)。

在控制器方法上添加@Secured 注解。

@PreAuthorize 和@PostAuthorize 都是方法或类级别注解

  • @PreAuthorize表示访问方法或类在执行之前先判断权限,大多情况下都是使用这个注解,注解的参数和access()方法参数取值相同,都是权限表达式。
  • @PostAuthorize 表示方法或类执行结束后判断权限,此注解很少被使用到。

开启注解

启动类:@EnableGlobalMethodSecurity(prePostEnabled = true)

在控制器方法上添加@Secured 注解。

十、RememberMe功能实现

Spring Security 中 Remember Me 为“记住我”功能,用户只需要在登录时添加remember-me复选框,取值为true。Spring Security 会自动把用户信息存储到数据源中, 以后就可以不登录进行访问。

添加依赖->配置数据源->编写配置类RememberMeConfig.java->修改SecurityConfig.java->在客户端页面添加复选框->有效时间(默认情况下重启项目后登录状态失效了。但是可以通过设置状态有效时间,即使项目重新启动下次也可以正常登录)

十一、Thymeleaf中SpringSecurity的使用

Spring Security 可以在一些视图技术中进行控制显示效果。例如:JSP或 Thymeleaf。 在非前后端分离且使用 Spring Boot 的项目中多使用 Thymeleaf作为视图展示技术。

Thymeleaf 对 Spring Security 的 支 持 都 放 在thymeleaf-extras-springsecurityX 中,目前最新版本为 5。所以需要在项目中添加此 jar 包的依赖和 thymeleaf 的依赖。

<dependency>
<groupId>org.thymeleaf.extrasgroupId>
<artifactId>thymeleaf-extras-springsecurity5artifactId>
dependency>

<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>

在 html 页面中引入 thymeleaf 命名空间和 security 命名空间

<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extrasspringsecurity5">

获取属性

可以在html页面中通过sec:authentication=""获取 UsernamePasswordAuthenticationToken中所有 getXXX的内容,包含父类中的 getXXX 的内容。

根据源码得出下面属性:

name:登录账号名称

principal:登录主体,在自定义登录逻辑中是 UserDetails

credentials:凭证

authorities:权限和角色

details:实际上是 WebAuthenticationDetails的实例。可以获取

remoteAddress(客户端 ip)和 sessionId(当前 sessionId)

新建demo.html->编写Controller

权限判断

设置用户角色和权限

控制页面显示效果:在页面中根据用户权限和角色判断页面中显示的内容

通过权限判断:
<button sec:authorize="hasAuthority('/insert')">新增button>
<button sec:authorize="hasAuthority('/delete')">删除button>
<button sec:authorize="hasAuthority('/update')">修改button>
<button sec:authorize="hasAuthority('/select')">查看button>
<br/>
通过角色判断:
<button sec:authorize="hasRole('abc')">新增button>
<button sec:authorize="hasRole('abc')">删除button>
<button sec:authorize="hasRole('abc')">修改button>
<button sec:authorize="hasRole('abc')">查看button>

退出登录:用户只需要向 Spring Security 项目中发送/logout退出请求即可。实现退出非常简单,只要在页面中添加/logout 的超链接即可。

点击退出后:

为了实现更好的效果,通常添加退出的配置。默认的退出 url 为/logout,退出成功后跳转到/login?logout。如果不希望使用默认值,可以通过下面的方法进行修改。

logout其他常用配置源码解读

LogoutConfigurer类:

1、页面跳转 2、销毁httpsession 3、清除认证状态 

内部调用了addLogoutHandler(LogoutHandler)默认是 contextLogoutHandler

clearAuthentication(boolean) 是否清除认证状态,默认为 true

invalidateHttpSession(boolean)是否销毁 HttpSession 对象,默认为 true

logoutSuccessHandler(LogoutSuccessHandler)退出成功处理器

也可以自己进行定义退出成功处理器。只要实现了LogoutSuccessHandler接口。与之前讲解的登录成功处理器和登录失败处理器极其类似。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值