Spring Security权限控制

一、Spring Security简介

1、概括

Spring Security是一个高度自定义的安全框架。利用Spring IOC/DI和AOP功能,为系统提供了声明式安全访问控制功能,减少了为系统安全而编写大量重复代码的工作。使用Spring Secruity的原因有很多,但大部分都是发现了javaEE的Servlet规范或EJB规范中的安全功能缺乏典型企业应用场景。同时认识到他们在 WAR或 EAR级别无法移植。因此如果你更换服务器环境,还有大量工作去重新配置你的应用程序。使用Spring Security解决了这些问题,也为你提供许多其他有用的、可定制的安全功能。正如你可能知道的两个应用程序的两个主要区域是“认证”和“授权”(或者访问控制)。这两点也是Spring Security重要核心功能。“认证”,是建立一个他声明的主体的过程(一个“主体”一般是指用户,设备或一些可以在你的应用程序中执行动作的其他系统),通俗点说就是系统认为用户是否能登录。“授权"指确定一个主体是否允许在你的应用程序执行一个动作的过程。通俗点讲就是系统判断用户是否有权限去做某些事情。

二、第一个Spring Security 项目

1导入依赖

Spring Security已经被Spring boot进行集成,使用时直接引入启动器即可。导入spring-boot-starter-security启动器后,Spring Security已经生效,默认拦截全部请求,如果用户没有登录,跳转到内置登录页面。

三、UserDetailsService详解

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

如果需要自定义逻辑时,只需要实现 UserDetailsService接口即可。接口定义如下:

    public interface UserDetailsService {
    UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;

四、 PasswordEncoder密码解析器详解

Spring Security 要求容器中必须有PasswordEncoder 实例。所以当自定义登录逻辑时要求必须给容器注入 PaswordEncoder 的 bean对象

1、接口介绍

encode():把参数按照特定的解析规则进行解析。

matches()验证从存储中获取的编码密码与编码后提交的原始密码是否匹配。如果密码匹配,则返回true;如果不匹配,则返回 false。第一个参数表示需要被解析的密码。第二个参数表示存储的密码。

upgradeEncoding():如果解析的密码能够再次进行解析且达到更安全的结果则返回true,否则返回 false。默认返回 false。

2 、BcryptPasswordEncoder简介

BCryptPasswordEncoder是 Spring Security 官方推荐的密码解析器,平时多使用这个解析器。

BCryptPasswordEncoder是对bcrypt强散列方法的具体实现。是基于Hash 算法实现的单向加密。可以通过strength控制加密强度,默认10.

五、自定义登录逻辑

当进行自定义登录逻辑时需要用到之前讲解的UserDetailsService和 PasswordEncoder。但是 Spring Security要求:当进行自定义登录逻辑时容器内必须有PasswordEncoder实例。所以不能直接new对象。

1、编写配置类

新建类com.example.config.SecurityConfig编写下面内容

@Bean
    public PasswordEncoder getPe(){
//        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        return new BCryptPasswordEncoder();
    }

六、自定义登录页面

虽然Spring Security给我们提供了登录页面,但是对于实际项目中,大多喜欢使用自己的登录页而。所以Spring Security 中不仅仅提供了登录页面,还支持用户自定义登录页面。实现过程也比较简单,只需要修改配置类即可。

别写登录页面,登录页面中<form>的 action不编写对应控制器也可以。

2、修改配置类

修改配置类中主要是设置哪个页面是登录页面。配置类需要继承WebSecurityConfigurerAdapte,并重写configure方法。

.successForwardUrl()登录成功后跳转地址

.loginPage()登录页面

.loginProcessingurl登录页面表单提交地址,此地址可以不真实存在。

antMatchers():匹配内容permitAll():允许

七、认证过程其他常用配置

1失败跳转

表单处理中成功会跳转到一个地址,失败也可以跳转到一个地中。

1.2修改表单配置

在配置方法中表单认证部分添加 failureForwardUrl()方法,表示登录失败跳转的url。此处依然是 POST请求,所以跳转到可以接收POST请求的控制器/fail 中。

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

2.1源码简介

当进行登录时会执行UsernamePasswordAuthenticationFilter 过滤器。

usernamePasrameter:账户参数名

passwordParameter:密码参数名

postOnly=true:默认情况下只允许POST 请求。

2.2、修改配置

 // 表单认证
        http.formLogin()
                .usernameParameter("username123") 重新配置username参数
                .passwordParameter("password123") c重新配置password参数  像这样重新配置后  就可以实现登录时改变参数
//                .failureHandler(new MyForwardAuthenticationFailureHandler("/fail.html"))
//                .failureForwardUrl("/fail")  // 用于处理登录失败实现页面跳转
                .loginProcessingUrl("/login") //当发现/login 时认为是登录,需要执行 UserDetailsServiceImpl
                .successHandler(new MyForwardAuthenticationSuccessHandler("toMain"))//通过ForwardAuthenticationSuccessHandler实现自定义跳转跳转到主页面而不是跳到localhost:8080
                //替换了 .successForwardUrl("/toMain") 因为该方法无法实现网址跳转自定义的方法可以实现网址和资源跳转
//                .successForwardUrl("/toMain") //此处是 post 请求
                .loginPage("/login.html");//自定义了登录页面
        // url 拦截
        http.authorizeRequests()//授权请求控制  antMatchers 是ant表达式

3自定义登录成功处理器

3.1源码分析

使用successForwardUrl()时表示成功后转发请求到地址。内部是通过successHandler ()方法进行控制成功后交给哪个类进行处理

public FormLoginConfigurer<H> successForwardUr1(String forwardUrl)

this.successHandler(new ForwardAuthenticationSuccessHandler(forwardUr1)) ;return this;

ForwardAuthenticationSuccessHandler内部就是最简单的请求转发。由于是请求转发,当遇到需要跳转到站外或在前后端分离的项目中就无法使用了。

4自定义登录失败处理器

4.1源码分析

failureForwardUrl()内部调用的是 failureHandler()方法

public FormLoginConfigurer<H> failureForwardUrl(String forwardUrl){
    this.failureHandler(new ForwardAuthenticationFailureHandler(
    forwardUrl)) ;
    return this;
}

ForwardAuthenticationFailureHandler 中也是一个请求转发,并在request 作用域中设置SPRING_SECURITY_LAST_EXCEPTION的 key ,

容为异常对象。

八、访问控制url匹配

在前面讲解了认证中所有常用配置,主要是对http.formLogin()进行操作。而在配置类中 http.authorizeRequests()主要是对url进行控制,也就是我们所说的授权(访问控制)。http.authorizeRequests()也支持连缀写法,总体公式为:

url匹配规则.权限控制方法

通过上面的公式可以有很多url 匹配规则和很多权限控制方法。这些内容进行各种组合就形成了Spring Security 中的授权。

在所有匹配规则中取所有规则的交集。

1、 anyRequest()

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

2、antMatcher()

方法定义如下:

public C antMatchers(String... antPatterns)

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

规则如下:

?匹配一个字符

*匹配0个或多个字符**匹配0个或多个目录

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

3、regexMatchers()

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

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

.regexMatchers(".+[.]js").permitAll() //意思是所有的.js都可以被访问到 regexMatchers 正则表达式进行匹配路径

.mvcMatchers()

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

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

容设置ServletPath spring.mvc.servlet. path=/com在Spring Security 的配置类中配置.servletPath()是 mvcMatchers()

返回值特有的方法,antMatchers()和 regexMatchers()没有这个方法。在 servletPath()中配置了servletPath 后,mvcMatchers()直接写

springMVC 中@RequestMapping()中设置的路径即可。

spring.mvc.servlet.path=/com

.regexMatchers("/demo").permitAll() //HttpMethod.POST 访问/demo时会报方法不被允许因为请求方式为GET   加入HttpMethod.POST 则表示只能通过POST请求
//                .mvcMatchers("demo").servletPath("/com").permitAll()
//                .antMatchers("/com/demo").permitAll()

九、内置访问控制方法介绍

Spring Security 匹配了URL后调用了permitAll()表示不需要认证,随意访问。在Spring Security中提供了多种内置控制。

permitAll()

permitAll()表示所匹配的URL任何人都允许访问。

public ExpressionInterceptUrlRegistry permitAll() {
   return access(permitAll);
}

十、角色权限判断

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

1 、hasAuthority(String)

判断用户是否具有特定的权限,用户的权限是在自定义登录逻辑中创建User对象时指定的。

下面代码中 admin就是用户的权限 admin严格区分大小写。

return new User(username,password,AuthorityUtils.commaSeparatedStringToAuthorityList ( "admin,ROLE_abc,/main.html,/insert,/delete"));

2、hasRole(String)

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

参数取值来源于自定义登录逻辑UserDetailsService 实现类中创建User对象时给User 赋予的授权。

在给用户赋予角色时角色需要以: ROLE_开头,后面添加角色名称。例如:ROLE_abc其中 abc是角色名,ROLE_是固定的字符开头。使用hasRole()时参数也只写abc即可。否则启动报错。

给用户赋予角色:

return new User(username,password,AuthorityUtils.commaSeparatedStringToAuthorityList ( "admin,ROLE_abc,/main.html,/insert,/delete"));

3、haslpAddress(String)

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

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

getRemoteAddr : 0:0:0:0:0:0:0:1

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

getRemoteAddr : 127.0.0.1

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

getRemoteAddr : 192.168.1.105

public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
    //获取参数
    User user = (User) authentication.getPrincipal();
    System.out.println(request.getRemoteAddr());
    System.out.println(user.getUsername());
    System.out.println(user.getPassword());//因为安全所以为null 密码进行了加密
    System.out.println(user.getAuthorities());//权限
    response.sendRedirect(url);

十一、自定义403处理方案

使用Spring Security时经常会看见403(无权限),默认情况下显示的效果如下:

而在实际项目中可能都是一个异步请求,显示上述效果对于用户就不是特别友好了。Spring Security支持自定义权限受限。

实现自定义无权限处理

@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {

        //简单的设置编码的方法
        response.setContentType("text/html;charset=utf-8");
        //设置响应时状态码
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        PrintWriter writer = response.getWriter();
        writer.print("{\"code\":\"403\",\"msg\":\"无权限\"}");
        writer.flush();
        writer.close();
    }
}

十二、基于表达式的访问控制

1、 access()方法使用

之前学习的登录用户权限判断实际上底层实现都是调用access(表达式)

2、使用自定义方法

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

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

@Service
public class MyServiceImpl implements MyService {

    @Override
    public boolean hasPerssion(HttpServletRequest request, Authentication authentication) {
        Collection<? extends GrantedAuthority> obj = authentication.getAuthorities(); //获取所有的权限

        return obj.contains(new SimpleGrantedAuthority(request.getRequestURI()));
    }
}

  1. 基于注解的访问控制

在Spring Security中提供了一些访问控制的注解。这些注解都是默认是都不可用的,需要通过@EnableGlobalMethodSecurity进行开启后使用。如果设置的条件允许,程序正常执行。如果不允许会报500

org.springframework. security.access.AccessDeniedException:不允许访问这些注解可以写到Service接口或方法上上也可以写到Controller或Controller的方法上。通常情况下都是写在控制器方法上的,控制接口URL是否允许被访问。

@Secured("abc") 

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

1.实现步骤

1.1开启注解

@Secured("abc") //使用@Secured("abc")注解只需要在需要控制的地方加上就可以实现控制
@Secured("abc") //使用@Secured("abc")注解只需要在需要控制的地方加上就可以实现控制
@EnableGlobalMethodSecurity(securedEnabled = true)
@SpringBootApplication
public class MyApp {
    public static void main(String[] args) {
        SpringApplication.run(MyApp.class, args);
    }
}

2.2 在控制器方法上添加@Secured

LoginController方法中添加注解

@Secured("abc")  //@Secured("abc")注解
@RequestMapping("/toMain")
public String toMain()
{
    return "redirect:/main.html";
}
//登录失败处理
//当登录失败后跳转到(@RequestMapping("/fail"))即此方法,并且执行此方法
@RequestMapping("/fail")
public String fail(){
    return "redirect:/fail.html";
}

  1. @PreAuthorize/@PostAuthorize

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

PreAuthorize表示访问方法或类在执行之前先判断权限,

大多

情况下都是使用这个注解,注解的参数和 access()方法参数取值相同,都是权限表达式。

@PostAuthorize表示方法或类执行结束后判断权限,此注解很少被使用到。

开启注解:

在启动类中开启@PreAuthorize注解

@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
@SpringBootApplication
public class MyApp {
    public static void main(String[] args) {
        SpringApplication.run(MyApp.class, args);
    }
}

  1. RememberMe功能实现

Spring Security 中 Remember Me为“记住我”功能,用户只需要在登录时添加remember-me复选框,取值为true。Spring Security 会自动把用户信息存储到数据源中,以后就可以不登录进行访问。即持久化到一个数据库中,然后下一次直接把信息取出来 持续登录

1、添加依赖

Spring Security 实现 Remember Me功能时底层实现依赖Spring-JDBC,所以需要导入Spring-JDBC。以后多使用MyBatis框架而很少直接导入spring-jdbc,所以此处导入mybatis启动器

同时还需要添加MySQL驱动和连接Mysql数据库

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

实现该功能时,需要有持久化的功能,所有需要实现该方法,在内置的方法中,有.tokenRepository(repository);//持久层对象 这个就是实现持久化的功能。所有我们需要找到PersistentTokenRepository 这个接口并且通过该接口实现持久层

@Bean
public PersistentTokenRepository getPersistentTokenRepository()
{
    JdbcTokenRepositoryImpl impl = new JdbcTokenRepositoryImpl();
    //在项目启动时创建存储rememberme的表
    //第二次启动时自动注释此代码
    impl.setCreateTableOnStartup(true);
    impl.setDataSource(dataSource);
    return impl;
}

2、http.RememberMe()功能中有

.tokenValiditySeconds(30) //设置登录的时间也就是remember-me的存储时间
                .userDetailsService(userDetailsService)//用户登录逻辑写在哪个对象中
                .tokenRepository(repository);//持久层对象 

所以我们需要注入userDetailsService,其中的.tokenRepository() 注入的是其源代码中的一个接口为PersistentTokenResposity但是因为它是一个接口 所以我们不能直接去new它,而是通过实现其中的一个实现类 叫做JdbcTokenRepositoryImpl 通过改实现类持久化到一个创库中即持久化到一Mysql数据库中 我们需要设置他的数据源 只需要设置数据源,其他的由框架自动完成 所以需要创建数据源对象 如下:

@Autowired
private DataSource dataSource;
@Bean
public PersistentTokenRepository getPersistentTokenRepository()
{
    JdbcTokenRepositoryImpl impl = new JdbcTokenRepositoryImpl();
    //在项目启动时创建存储rememberme的表
    //第二次启动时自动注释此代码
    impl.setCreateTableOnStartup(true);
    impl.setDataSource(dataSource);
    return impl;
}

有了以上的数据源的注入我们可以在当前的项目中注入一个resposity对象 即持久化对象

虽然完成了上述的配置后,有了数据源和持久层,但是我们发现我们没有数据库进行存储,即存不到我们用户登录的信息。所以可以怎么办呢?在改功能中有一个方法,叫做impl.setCreateTableOnStartup(); 意思是在项目启动时,帮助创建一个rememberMe的表,第二次启动时需要注释改代码,否则报错 以上完成后 我们就完成了rememberMe的功能,但是我们可以发现,rememberMe功能有一个时间段,超过该时间段过后,需要重新进行登录,我们可以通过叫做.tokenValiditySeconds() 来实现rememberMe的时间,其默认值为两周

十五、Thymeleaf中Spring Security的使用

SpringSecurity可以在一些视图技术中进行控制显示效果。例如:

JSP或Thymeleaf。在非前后端分离且使用SpringBoot的项目中多使

用Thymeleaf作为视图展示技术。Thymeleaf对Spring Security的支持都放在thymeleaf- extras-springsecurityX中,需要在项目中添加此jar包的依赖和thymeleaf的依赖。

略。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

这是学习的时候做的笔记,上传到CSDN上面,不会丢失。。。。。。。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值