欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

SpringSecurity

程序员文章站 2024-03-19 13:40:04
...

SpringBoot下使用SpringSecurity
(一)基本
1.SpringSecurityJWT学习博客

https://blog.csdn.net/larger5/article/details/81047869

https://blog.csdn.net/larger5/article/details/81063438

2.设置不需要登录就能访问接口
在SpringSecurityConfig里面配置
@Override
public void configure(WebSecurity web) throws Exception {

web.ignoring().antMatchers("/demo/noLogin");/设置不需要登录就能访问的url/

//web.ignoring().antMatchers("/info","/health","/hystrix.stream");//可以设置多个url
}

3.在内存里面添加用户
/**

  • 用户自定义安全认证

  • @param auth

  • @throws Exception
    */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

    /*在内存里面添加管理员用户 用户名是admin 密码是123456 角色是admin角色

    • 然后就可以用这个账号密码进行登录操作了
    • */
      auth.inMemoryAuthentication()
      .passwordEncoder(new MyPasswordEncoder())
      .withUser(“admin”).password(“123456”).roles(“admin”);

}
4.从数据库里面获取用户的权限提供给SpringSecurity

UserDetailsService 是获取用户权限信息的

/**

  • SpringSecurity 处理用户登录逻辑
    */
    @Component
    public class MyUserDetailsService implements UserDetailsService {
    @Autowired
    private SysUserDao sysUserDao;
    @Autowired
    private SysRoleDao sysRoleDao;

    /**

    • 从数据库取出当前登录的用户名角色和权限提供给SpringSecurity框架.
    • @param username 登录用户名 (登录的用户名)
    • @return
    • @throws UsernameNotFoundException
      */
      @Override
      public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
      /通过登录账号查询是否有该用户/
      Map sysUserMap = this.sysUserDao.selectSysUserByMobilePhone(username);
      /*如果没用该用户,直接返回null.SpringSecurity会根据返回结果来判断
      • 是否有该用户来进行相应的操作
      • */
        if (sysUserMap == null) {
        return null;
        }
        /准备装用户的权限的集合容器/
        List grantedAuthorityList = new ArrayList<>();
        /通过当前登录用户的用户id查询用户的权限/
        List roleMapList = this.sysRoleDao.selectRoleByUserId(sysUserMap.get(“id”).toString());
        /如果有权限就开始组装数据提供给SpringSecurity框架来使用/
        if (roleMapList != null) {
        for (Map map : roleMapList) {
        grantedAuthorityList.add(new SimpleGrantedAuthority(map.get(“roleName”).toString()));
        }
        }
        /返回user对象/
        return new User(username, sysUserMap.get(“password”).toString(), grantedAuthorityList);
        }
        }

5.注解方式拦截url
可以作用于在类上面, 只有符合角色的用户才能访问当前类的方法
也可以作用于在方法上面,只有符合角色的用户才能访问当前方法

注解方式
现在启动类我上加注解

@MapperScan(basePackages = “com.zjj.dao”)
@SpringBootApplication
@RestController
@EnableGlobalMethodSecurity(prePostEnabled = true)//该注解意思是让@PreAuthorize起作用
public class LmlApplication {

在方法上添加注解,表示该方法哪些角色可以访问

@PreAuthorize(“hasAnyAuthority(‘ROLE_admin’,‘ROLE_user’)”)//多个角色限制
@RequestMapping("/userAuthority")
public String userAuthority() {
return “这个是普通用户权限,管理员和普通用户都可以访问”;
}

@PreAuthorize(“hasAuthority(‘ROLE_admin’)”)//单个角色限制
@RequestMapping("/adminAuthority")
public String adminAuthority() {
return “这个是管理员权限,普通用户不能访问”;
}
在类上添加注解,标识该类所有的方法主要是该角色都可以访问该注解

@RestController
@PreAuthorize(“hasAuthority(‘ROLE_admin’)”) //只有管理员才能访问这个controller的东西
@RequestMapping("/admin")
public class AdminController {
/**
* admin可以访问该方法
* 但是还是登录的
* @return
*/
@RequestMapping("/adminCanVisit")
public String noAuthority() {
return “管理员可以访问该角色”;
}
}

还有其它的url

/**

  • 指定权限才能访问
  • 意思是admin权限可以访问 ,注意 前面必须要带 ROLE_ , 不然系统无法区分
  • 也可以使用 and or 表达式
    */
    @PreAuthorize(“hasRole(‘ROLE_ADMIN’)”)
    @RequestMapping("/roleAuth")
    public String role() {
    return “admin auth”;
    }

/**

  • @PreAuthorize: 期望对传过来的参数做限制,
  • 比如说要让id小于10 , 可以查询限制的信息
  • 比如username 必须等于当前的登录用户,
  • 比如说希望user必须是某一个用户, 可以equals一个人的名字(abc)
  • @PostAuthorize : 是对方法调用完场之后对权限进行检查的,不能控制方法是否能被调用,
  • 只能在方法调用权限决定是否要抛出异常
  • 比如我期望是一个偶数,returnObject取出当前的返回值 ,然后对2取余等于0就是偶数
    */
    @PreAuthorize("#id<10 and principal.username.equals(#username) and #user.username.equals(‘abc’)")
    @PostAuthorize(“returnObject%2==0”)
    @RequestMapping("/test")
    public Integer test(Integer id, String username, User user) {
    // …
    return id;
    }

/*
@PreFilter 是对集合类型的参数进行过滤
filterObject 是list对象,
@PostFilter是对集合类型的返回值进行过滤
返回值必须被4整除
*/
@PreFilter(“filterObject%20")
@PostFilter("filterObject%4
0”)
@RequestMapping("/test2")
public List test2(List idList) {
// …
return idList;
}

概念

1.什么是权限
就是不同的用户他所处的角色不一样, 比如普通员工和财务会计,你不能让普通员工访问财务 会计才能访问的网址,所以就需要限制这个行为.
简单说就是不同的用户,让他登录系统时候显示的菜单不一样.财务人员登录显示财务相关模块,HR登录让他显示HR相关的模块
如果用户没有权限就让它无法访问.超出它的访问范围了

2.为什么需要权限
安全性
误操作 ,人为破坏 ,数据泄露等
比如说数据库的: 开发人员和 DBA 如果数据库有问题开发人员可能会随便顺手改了,这样是不行的,数据会越改越乱,正常情况下是应该是发邮件给DBA,然后让DBA去改,而不是开发人员去改. 应该给开发人员只读权限,给DBA全部权限.
比如服务器:ROOT用户和普通用户, 普通用户登录上去可能是看日志什么的,如果给开发人员太多权限就没必要,万一开发人员上去删除个东西删除命令错了给整合系统都删除崩溃了问题就大了.
因此,普通用户权限应该是受限制的
数据隔离
比如说不同的领导能看到自己手下的信息,其他部门的领导就没必要看到了,再比如公司发工资信息只能是财务部门可以看到,其他人不能看到,工资是保密的.
明确职责
运营,客户等不同的角色,leader和dev等不同级别
比如客服能查到所有的订单,而运营只要能查到自己负责的店铺订单就可以了.客服可以看到用户购买产品的一些反馈情况,相反,运营就看不到了.相反,运营能看到店铺的运行情况,客服就看不到了
再比如,leader可以清理权限,而普通开发人员就不需要这些东西.
3.权限模型
用户-权限(MySQL用)
人员少,功能固定,或者特别简单的系统
RBAC模型(企业基本用这个)
Role-Based Access Control
用户-角色-权限 ,都适用
基于角色的权限访问控制.
权限与角色相关联

角色是为了完成某种工作而创建出来的,用户则依据他的责任和资格被指派成相应的角色,同时用户可以很容易的从一个角色被指派成另一个角色,角色可根据新的需求和系统的合并来赋予新的权限,而权限也可以从某个角色中回收,角色与角色之间的关系可以建立起来覆盖更广泛的客观清理.’’

(二)原理
1.简介
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

Spring Security 的前身是 Acegi Security ,是 Spring 项目组中用来提供安全认证服务的框架。
Spring Security 为基于J2EE企业应用软件提供了全面安全服务。特别是使用领先的J2EE解决方案-Spring框架开发的企业软件项目。[1]
2.核心功能

认证 (你是谁)

授权(你能干什么,权限控制)

攻击防护(防止伪造身份)

3.能干什么
SpringSecurity是一个权限管理框架,能根据账户所拥有的角色来限制账户访问的页面,同时可以限制不符合权限要求的账户通过url访问越权的页面(比如普通用户不能通过url方式访问财务系统.)+

Spring Security对Web安全性的支持大量地依赖于Servlet过滤器。这些过滤器拦截进入请求,并且在应用程序处理该请求之前进行某些安全处理。 Spring Security提供有若干个过滤器,它们能够拦截Servlet请求,并将这些请求转给认证和访问决策管理器处理,从而增强安全性。根据自己的需要,可以使用适当的过滤器来保护自己的应用程序。

如果使用过Servlet过滤器且令其正常工作,就必须在Web应用程序的web.xml文件中使用

FilterToBeanProxy是一个特殊的Servlet过滤器,它本身做的工作并不多,而是将自己的工作委托给Spring应用程序上下文 中的一个Bean来完成。被委托的Bean几乎和其他的Servlet过滤器一样,实现javax.servlet.Filter接 口,但它是在Spring配置文件而不是web.xml文件中配置的。

实际上,FilterToBeanProxy代理给的那个Bean可以是javax.servlet.Filter的任意实现。这可以是 Spring Security的任何一个过滤器,或者它可以是自己创建的一个过滤器。但是正如本书已经提到的那样,Spring Security要求至少配置四个而且可能一打或者更多的过滤器。
4.优点
人们使用Spring Security有很多种原因,不过通常吸引他们的是在J2EE Servlet规范或EJB规范中找不到典型企业应用场景的解决方案。

特别要指出的是他们不能在WAR 或 EAR 级别进行移植。

这样,如果更换服务器环境,就要,在新的目标环境进行大量的工作,对应用系统进行重新配置安全。

使用Spring Security 解决了这些问题,也提供很多有用的,完全可以指定的其他安全特性。

可能知道,安全包括两个主要操作。

第一个被称为“认证”,是为用户建立一个他所声明的主体。主体一般是指用户,设备或可以在系统中执行动作的其他系统。

第二个叫“授权”,指的是一个用户能否在应用中执行某个操作,在到达授权判断之前,身份的主体已经由身份验证过程建立。

这些概念是通用的,不是Spring Security特有的。

在身份验证层面,Spring Security广泛支持各种身份验证模式,这些验证模型绝大多数都由第三方提供,或者正在开发的有关标准机构提供的,例如 Internet Engineering Task Force.

作为补充,Spring Security 也提供了自己的一套验证功能。

Spring Security 目前支持认证一体化如下认证技术:

HTTP BASIC authentication headers (一个基于IEFT RFC 的标准)

HTTP Digest authentication headers (一个基于IEFT RFC 的标准)

HTTP X.509 client certificate exchange (一个基于IEFT RFC 的标准)

LDAP (一个非常常见的跨平台认证需要做法,特别是在大环境)

Form-based authentication (提供简单用户接口的需求)

OpenID authentication

Computer Associates Siteminder

JA-SIG Central Authentication Service (CAS,这是一个流行的开源单点登录系统)

Transparent authentication context propagation for Remote Method Invocation and HttpInvoker (一个Spring远程调用协议)[1]

5.工作原理
依赖于过滤器,通过这些过滤器拦截进入请求,判断是否已经登录认证且具访问对应请求的权限.
要完成访问控制.
6.认证和授权概念
认证: 就是确认身份,登录就是认证,如果想用系统中的功能就必须要登录来确定身份(Spring Security框架的功能都是基于认证.)
授权: 登录成功后判断用户是否具有对应的权限(普通用户,管理员用户)
授权是基于认证的,只有认证成功才有授权.
7.SpringSecurity优缺点
优点
提供了一套安全框架,
提供了很多用户认证的功能,实现相关接口即可,节约大量开发工作,
基于Spring,容易集成Spring项目中,且封装了许多方法
缺点
配置文件多,角色被’编码’到了配置文件和源文件中,RBAC不明显

对于系统中的用户,角色,权限之间的关系没有可以操作的界面

大数据量的情况下,几乎不可用.

8.SpringSecurity执行流程
spring security里面的四个重要的类,都需自己去实现:

1、UserDetailsService 读取登录用户信息、权限

2、AbstractSecurityInterceptor 这个类是用来继承的,还要实现servler的Filter,作用过滤url

3、FilterInvocationSecurityMetadataSource 读取url资源

4、AccessDecisionManager 控制访问权限

说明 :蓝色框是类,黄色框是对应的类的方法,绿色是发送url请求
流程图:

9.FilterChainProxy类

会按照顺序调用一组Filter,使这些Filter即能完成验证授权的本质工作用能享用Spring IOC的功能来很方便的得到其它依赖的资源
10.UserDetails类
代表用户安全信息的源,该接口封装了登录的时候所需要的所有信息

这几个方法中任何一个返回false,用户的凭证就会被视为无效

getAuthorities () 获取权限信息

下面四个boolean方法是用来让你自己执行校验逻辑的,你需要把校验逻辑的结果通过这个方法的实现返回回去
isAccountNonExpired()
是你的账户没有过期,如果返回true就是没有过期,否则就是过期了,至于怎么判断需要你自己实现,你也可以永远返回true就行了.
isAccountNonLocked()
是你的密码是否过期了 有的网站安全性可能比较高,会要求你30 天或者7天换个密码,这个方法可以告诉SpringSecurity框架你的密码是否过期了.
一般在业务表示这个账户是否被冻结了,被冻结了就返回false,

isCredentialsNonExpired()
你的账户是不是被锁定了(没有什么业务意义,你只要返回false就会抛出相应的异常)
isEnabled()
你的账户是不是可用的(没有什么业务意义,你只要返回false就会抛出相应的异常)
这个方法和第二个是有区别的, 这个是告诉框架用户是否被删除了,因为在很多系统中我们用户的信息是不会被真正的删除掉的,而是逻辑删除,
区别是冻结的用户是可以恢复的,被删除的用户一般是不能恢复的.

11.Authentication对象

public interface Authentication extends Principal, Serializable {

/** 获取权限集合 */
Collection<? extends GrantedAuthority> getAuthorities();

/*获取凭证/
Object getCredentials();

/*获取认证的一些额外信息/
Object getDetails();

/**获取认证的实体 */
Object getPrincipal();

/*是否认证通过/
boolean isAuthenticated();

/**

*/

void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

是进行安全访问控制用户信息的安全对象,Authentication有未认证和已认证的两种状态,在作为参数传入认证管理器的时候,它是未认证的,它从客户端获取用户的身份信息,如用户名密码,可以是从一个登录页面,也可以是cookie中获取

12.权限缓存

13.自定义决策

14.指定用户可以访问接口

/**

  • 指定权限才能访问
  • 意思是admin权限可以访问 ,注意 前面必须要带 ROLE_ , 不然系统无法区分
    */
    @PreAuthorize(“hasRole(‘ROLE_ADMIN’)”)
    @RequestMapping("/roleAuth")
    public String role() {
    return “admin auth”;
    }

还需要在启动类配置注解

@EnableGlobalMethodSecurity(prePostEnabled = true)//该注解意思是让@PreAuthorize起作用

@SpringBootApplication
@RestController
@EnableAutoConfiguration //SpringBoot 根据jar包依赖自动配置项目
@EnableGlobalMethodSecurity(prePostEnabled = true)//该注解意思是让@PreAuthorize起作用
public class DemoApplication {

15.自定义密码校验器
SpringSecurity原生的加密方式是随机生成的盐,然后在生成随机密码串的时候会把随机的盐混在这个密码串儿里面
同样的密码加密出来的随机生成的盐是不一样的, 推荐用默认的.
定义

/**

  • 密码自定义验证
    */
    public class MyPasswordEncoder implements PasswordEncoder {

    /**

    • 对密码进行加密并返回
    • @param rawPassword
    • @return
      */
      @Override
      public String encode(CharSequence rawPassword) {
      return rawPassword.toString();
      }

    /**

    • 验证密码是否正确
    • @param rawPassword 用户输入的原生未加密密码
    • @param encodedPassword 从数据库取出来的加密的密码
    • @return
      */
      @Override
      public boolean matches(CharSequence rawPassword, String encodedPassword) {
      // aes加密, 也可以指定其他的
      String decode = AESUtils.decode(String.valueOf(encodedPassword));
      //拿原生的密码和解密后的密码进行对比, 只要返回是true就是验证成功
      return rawPassword.equals(decode);
      }
      }
      使用
      auth.userDetailsService(myUserService).passwordEncoder(new MyPasswordEncoder());

      @Configuration
      public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
      的下面方法内使用.
      @Override
      protected void configure(AuthenticationManagerBuilder auth) throws Exception {
      /指定自己选用的userService类/
      auth.userDetailsService(myUserDetailsService).passwordEncoder(new MyPasswordEncoder());
      }

16.权限表达式注解
@PreAuthorize(“hasRole(‘ROLE_ADMIN’)”)
@PostAuthorize(“returnObject%20")
@PreFilter("filterObject%2
0”)
@PostFilter(“filterObject%4==0”)

/**

  • 指定权限才能访问
  • 意思是admin权限可以访问 ,注意 前面必须要带 ROLE_ , 不然系统无法区分
  • 也可以使用 and or 表达式
    */
    @PreAuthorize(“hasRole(‘ROLE_ADMIN’)”)
    @RequestMapping("/roleAuth")
    public String role() {
    return “admin auth”;
    }

/**

  • @PreAuthorize: 期望对传过来的参数做限制,
  • 比如说要让id小于10 , 可以查询限制的信息
  • 比如username 必须等于当前的登录用户,
  • 比如说希望user必须是某一个用户, 可以equals一个人的名字(abc)
  • @PostAuthorize : 是对方法调用完场之后对权限进行检查的,不能控制方法是否能被调用,
  • 只能在方法调用权限决定是否要抛出异常
  • 比如我期望是一个偶数,returnObject取出当前的返回值 ,然后对2取余等于0就是偶数
    */
    @PreAuthorize("#id<10 and principal.username.equals(#username) and #user.username.equals(‘abc’)")
    @PostAuthorize(“returnObject%2==0”)
    @RequestMapping("/test")
    public Integer test(Integer id, String username, User user) {
    // …
    return id;
    }

/*
@PreFilter 是对集合类型的参数进行过滤
filterObject 是list对象,
@PostFilter是对集合类型的返回值进行过滤
返回值必须被4整除
*/
@PreFilter(“filterObject%20")
@PostFilter("filterObject%4
0”)
@RequestMapping("/test2")
public List test2(List idList) {
// …
return idList;
}
17.所有权限表达式汇总

表达式 说明
permitAll 永远返回true
denyAll 永远返回false
anonymous 当前用户是anonymous时返回true
rememberMe 当前用户是rememberMe用户时返回true
authenticated 当前用户不是anonymous时返回true
fullAuthenticated 当前用户既不是anonymous也不是rememberMe用户时返回true
hasRole(role) 用户拥有指定的角色权限时返回true
hasAnyRole([role1,role2]) 用户拥有任意一个指定的角色权限时返回true
hasAuthority(authority) 用户拥有指定的权限时返回true
hasAnyAuthority([authority1,authority2]) 用户拥有任意一个指定的权限时返回true
hasIpAddress(‘192.168.1.0’) 请求发送的Ip匹配时返回true

这些权限表达式都是跟着AntMatchers方法匹配器后面的

18.自定义权限表达式
如果你想让某个url是admin角色,同时是某某某ip才能访问的,你需要自定义权限表达式.

在SpringConfig类里面用access()方法里面写权限表达式

也可以自己写代码来判断,

19.csrf
Spring Security 4.0之后,引入了CSRF,默认是开启。不得不说,CSRF和RESTful技术有冲突。CSRF默认支持的方法: GET|HEAD|TRACE|OPTIONS,不支持POST。
所以需要关闭csrf

(三)SpringSecurity过滤器链
1.图解
绿色过滤器是负责身份验证的

SpringSecurity 采用的是责任链的设计模式,它有一条很长的过滤器链。现在对这条过滤器链的各个进行说明:
2.SecurityContextPersistenceFilter
这个过滤器是过滤器的顶端,第一个起作用的过滤器,用途,在执行其它过滤器之前先判断用户的session是否已经存在SpringSecurity上下文的SecurityContext,如果存在就拿出来放到SecurityContextHolder 中来提供SpringSecurity的其它部分组件来使用,如果不存在就创建一个SecurityContext出来,还是放到SecurityContextHolder中给SpringSecurity其它组件使用
第二个用途是在所有过滤器执行完毕之后清空SecurityContextHolder中的内容.因为SecurityContextHolder是基于ThreadLocal的,如果在操作完成之后没有清空ThreadLocal会受到服务器的线程池机制的影响

HeaderWriterFilter:用于将头信息加入响应中。

CsrfFilter:用于处理跨站请求伪造。

3.LogoutFilter
只是用于处理退出登录,用途,在用户发送注销请求的时候销毁用户的session,清空SecurityContextHolder ,然后重定向到注销成功页面,也可以在注销成功之后清空用户的cookie

4.AbstractAuthenticationProcessionFilter
处理form登录的过滤器,form登录的所有操作都在这个过滤器进行的
5.DefaultLoginPageGeneratingFilter
用来生成默认的登录页面,基本不用,因为太难看了,只能是在学习阶段演示时候使用.
6.BasicAuthenticationFilter
检测和处理 http basic 认证,功能和AbstractAuthenticationProcessionFilter很类似,只是验证的方式不同
7.SecurityContextHolderAwareRequestFilter
主要是包装请求对象request(客户请求),目的是在原来的请求基础上为后续程序提供一些额外的数据
8.RememberMeAuthenticationFilter
当用户没有登录而直接访问资源时, 从 cookie 里找出用户的信息, 如果 Spring Security 能够识别出用户提供的remember me cookie, 用户将不必填写用户名和密码, 而是直接登录进入系统,该过滤器默认不开启。
依赖cookie来实现的.
9.AnonymousAuthenticationFilter
保证操作统一性,当用户没有登录时,默认为用户分配匿名用户的权限.当然,许多项目会关闭匿名用户.
在图解绿色过滤器链中,不管前面有多少个绿色过滤器,后面一定会有这个过滤器,

这个类主要做的事情:
判断前面过滤器是否成功的进行身份认证,或者从session里面拿到认证信息,如果前面都没成功的话,这个过滤器就会创建一个AnonymousAuthenticationToken 匿名的token,然后放到SecurityContext里面,不管是否认证通过,到最后SecurityContext里面一定会有一个Authentication,提供FilterSecurityInterceptor使用.

10.ExceptionTranslationFilter

处理 AccessDeniedException 和 AuthenticationException 异常,将请求重定向到对应页面或返回对应的错误代码
在开发中都会对全局异常进行处理.

11.SessionManagementFilter
主要用于防御会话伪造攻击,用户登录成功之后,销毁用户的当前session,并重新生成一个session就可以了.
管理 session 的过滤器
12.FilterSecurityInterceptor
这个过滤器负责最终来决定当前请求是否能通过整个过滤器链来访问接口,如果不能会根据不能的原因抛出不同的异常,然后由前面的过滤器(ExceptionTranslationFilter)来处理.

用户的权限控制都包含在这个过滤器中
第一个功能是如果用户没登录抛出尚未认证异常
第二个功能是如果用户已经登录但是没有访问当前资源的权限,就会抛出拒绝访问的异常
第三个功能如果用户已经登录,也具有当前资源的权限,那么就放行

13.ChannelProcessingFilter
制定必须为https连接;

//**************

WebAsyncManagerIntegrationFilter:将 Security 上下文与 Spring Web 中用于处理异步请求映射的 WebAsyncManager 进行集成。

UsernamePasswordAuthenticationFilter:用于处理基于表单的登录请求,从表单中获取用户名和密码。默认情况下处理来自 /login 的请求。从表单中获取用户名和密码时,默认使用的表单 name 值为 username 和 password,这两个值可以通过设置这个过滤器的usernameParameter 和 passwordParameter 两个参数的值进行修改。

RequestCacheAwareFilter:用来处理请求的缓存。

/***************************

SpringSecurity
(一)使用上的一些坑
1.角色名字特殊要求 前缀是 ROLE_

这个是SpringSecurity默认的登录的name属性,如果想修改需要修改配置文件
2.如果登录成功之后,用户使用功能之后就预先判断改用户是否具有该权限.
使用中出现的问题

  1. 输入用户名密码正确还是登录失败被拦截
    解决问题,更换更高版本的坐标依赖.
    原先使用的是4.0.3.RELEASE版本,. 更换5.0.1.RELEASE版本问题解决

2.登录的form表单action地址必须写框架提供的地址,然后必须是post请求

要不然登录的时候会被一直拦截.就是输入用户名密码正确还无法进入首页.

(二)认证使用步骤
1.坐标依赖

org.springframework.security spring-security-web 5.0.1.RELEASE org.springframework.security spring-security-config 5.0.1.RELEASE org.springframework.security spring-security-core 5.0.1.RELEASE org.springframework.security spring-security-taglibs 5.0.1.RELEASE 2.web.xml配置加载资源和委派过滤器 contextConfigLocation classpath:applicationContext.xml, classpath:spring-security.xml 委派过滤器是程序的入口必须得配置,固定的,所有的不需要修改,直接粘过去即可(名字也不能修改,不然框架找不到) springSecurityFilterChain org.springframework.web.filter.DelegatingFilterProxy springSecurityFilterChain /* 3.配置spring-security.xml <?xml version="1.0" encoding="UTF-8"?>
<!-- 不拦截的资源 不能拦截登录页面
pattern="/login.jsp"  写页面地址
security="none" 意思是不拦截

注意,配置的时候路径一定要写对,不要落下 / (斜杠) ,不然也无法进入页面
并且一直进不到页面里面去
–>
<security:http pattern="/login.jsp" security=“none”/>
<security:http pattern="/failer.jsp" security=“none”/>

<!-- 把一些静态资源不拦截 -->
<security:http pattern="/css/**" security="none"/>
<security:http pattern="/img/**" security="none"/>
<security:http pattern="/plugins/**" security="none"/>

<!-- 配置拦截的规则
 auto-config="true" 使用默认的登录页面(框架自己提供的)
  use-expressions="true" 使用Spring的EL表达式
 -->
<security:http auto-config="true" use-expressions="true">
    <!-- 配置拦截的规则
      pattern="/**" 配置路径
      access       要求 就是拦截规则的编写
      下面意思就是要求你先登录然后必须要有ROLE_USER 角色
     -->
    <security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER','ROLE_ADMIN')"/>
    <!-- 配置自己的登录页面
      login-page="/login.jsp" 登录的页面
      login-processing-url="/login"  是唯一的登录入口,如果你想让框架帮你校验就得在                                             form表单等等写这个地址
     default-target-url="/index.jsp" 如果登录成功默认往哪里跳转
     authentication-failure-url="/failer.jsp" 认证失败了往哪里跳转
    -->
    <security:form-login
            login-page="/login.jsp"
            login-processing-url="/login"
            default-target-url="/index.jsp"
            authentication-failure-url="/failer.jsp"
    />
    <!-- 关闭跨域攻击 -->
    <security:csrf disabled="true"/>
    <!-- 退出的请求地址 -->
    <security:logout logout-url="/logout" logout-success-url="/login.jsp"/>
</security:http>

<!-- 提供假的用户,后期切换成数据库 -->
<security:authentication-manager>
    <security:authentication-provider>
        <security:user-service>
                <!--password="{noop}111" {noop} 意思是告诉框架是明文的,不是密文的
                如果不写它会把这个密码加密,你会永远都登录不上去
                authorities="ROLE_USER" 是权限
                -->
            <security:user name="aaa" password="{noop}111" authorities="ROLE_USER"/>
        </security:user-service>
    </security:authentication-provider>
</security:authentication-manager>
4.在登录页面编写form表单 注意,请求路径必须要写spring-security.xml里面 login-processing-url="/login" 配置的唯一请求入口

jsp页面版本
注意form表单的action属性 和method属性
还有用户名name=“username” 和 密码 name=“password” 是固定的,不要写错了,如果写成别的配置文件还需要修改一下才行

登录
html页面版本
            <div class="form-group has-feedback">
                <input type="text"  name="username" class="form-control" placeholder="用户名">
             
            </div>
            
            <div class="form-group has-feedback">
                <input type="password"  name="password" class="form-control" placeholder="密码">
                <span class="glyphicon glyphicon-lock form-control-feedback"></span>
            </div>
            
            <div class="row">
                <div class="col-xs-8">
                    <div class="checkbox icheck">
                        <label><input type="checkbox"> 记住 下次自动登录</label>
                    </div>
                </div>
                <!-- /.col -->
                <div class="col-xs-4">
                    <button type="submit" class="btn btn-primary btn-block btn-flat">登录</button>
                </div>
                <!-- /.col -->
            </div>
        </form>

5.从数据库里面获取用户名和密码来进行登录
上面demo是假数据,下面用从数据里面查询的真数据用户名和密码来进行完成用户登录
需要在上面的基础上进行改造或者配置文件重新写
配置文件//************

<?xml version="1.0" encoding="UTF-8"?>

<!--配置不拦截的请求-->
<security:http pattern="/login.jsp" security="none"/>
<security:http pattern="/failer.jsp" security="none"/>
<!--不拦截静态资源-->
<security:http pattern="/css/**" security="none"/>
<security:http pattern="/img/**" security="none"/>
<security:http pattern="/plugins/**" security="none"/>
<!--配置拦截规则-->
<security:http auto-config="true" use-expressions="true">
    <security:intercept-url pattern="/**" access="hasAnyRole('ROLE_ADMIN','ROLE_USER')"/>
    <!--配置自己登录的页面
            login-page="/login.jsp"  登录页面
            login-processing-url="/login" 登录的URL路径
            default-target-url="/index.jsp"
            authentication-failure-url="/failer.jsp"
    -->
    <security:form-login
            login-page="/login.jsp"
            login-processing-url="/login"
            default-target-url="/index.jsp"
            authentication-failure-url="/failer.jsp"
    />
    <!--关闭跨域攻击-->
    <security:csrf disabled="true"/>
    <!--退出登录请求地址-->
    <security:logout logout-url="/logout" logout-success-url="/login.jsp"/>
</security:http>
<!--配置加密类-->
<bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"></bean>
<!-- 给框架提供数据的方式的是读取数据库中的数据 -->
<security:authentication-manager>
    <!-- 下面配置的意思是: 登录提供者

user-service-ref : 用户服务引用:需要引用IOC里面的bean,这个bean是被动等待方法(继承了 UserDetailsService 类的实现类)
–>
<security:authentication-provider user-service-ref=“sysUserServiceImpl”>

<security:password-encoder ref=“passwordEncoder”></security:password-encoder>
</security:authentication-provider>
</security:authentication-manager>

java代码//**************
接口需要继承UserDetailsService //UserDetailsService 是Spring security框架提供的类
public interface SysUserService extends UserDetailsService {
实现类需要实现UserDetailsService 里面的一个方法

实现类方法//*******
public class SysUserServiceImpl implements SysUserService {
/**
* 给框架提供数据
* 密码是加密的
* 角色是从数据库获取的
*/
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

	// 创建List集合,存储用户拥有的角色
	List<GrantedAuthority> list = new ArrayList<>();
	// list.add(new SimpleGrantedAuthority("ROLE_USER"));
	
	// 通过用户名查询数据(用户名是唯一的)
	SysUser sysUser = sysUserDao.findUserByUsername(username);
	// 如果为空
	if(sysUser == null) {
		// 说明,没有查询到
		return null;
	}
	
	// 获取用户拥有的角色
	List<SysRole> roles = sysUser.getRoles();
	// 判断
	if(roles != null && roles.size() > 0) {
		// 集合中有东西
		for (SysRole sysRole : roles) {
			//把RoleName放入集合中
			list.add(new SimpleGrantedAuthority(sysRole.getRoleName()));
		}
	}
	// 返回密码是密文
	User user = new User(username,sysUser.getPassword(),list);
	// 返回
	return user;
}

然后访问工程/login进入一个页面就可以进行登录验证了
/login是spring-security.xml配置好的
login-processing-url="/login"
(三)其它使用
1.SpringSecurity对密码加密(类似md5)
需要依赖:

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

加密
BCryptPasswordEncoder b = new BCryptPasswordEncoder();
b.encode(“123”));
解密:
BCryptPasswordEncoder b = new BCryptPasswordEncoder();
b.matches(“123”, “$2a1010Yt5hkYraxjrgI5Snx6EPpedycQ4NJTBft/fGvM1juh81ikYH22fui”)
public static void main(String[] args) {
BCryptPasswordEncoder b = new BCryptPasswordEncoder();
// $2a1010LdtV.h3I/bgdgDqhH53wX.TVyEBJcMgGkkWTQFm1JLv574PASVKYC
// $2a1010Yt5hkYraxjrgI5Snx6EPpedycQ4NJTBft/fGvM1juh81ikYH22fui
// 对密码进行加密
// System.out.println(b.encode(“123”)); //加密

// 提供了一个方法进行判断
System.out.println(b.matches(“123”, “$2a1010Yt5hkYraxjrgI5Snx6EPpedycQ4NJTBft/fGvM1juh81ikYH22fui”)); //进行判断的
}
2.获取当前登录的用户名显示在页面上
jsp方式(引入标签库)
可以展示在页面
最简单的办法:
使用security框架提供方式获取
<%@ taglib prefix=“security” uri=“http://www.springframework.org/security/tags” %>

在jsp页面加入
<security:authentication property=“principal.username”/>
效果:

HTML方式(因为不是jsp,不能引入标签库,所以只能说从后台查出来传给前台来回显)

后台代码方式:
Controller层:
注意,必须要写在controller层, 在service层无法获取登录用户名.

import java.util.HashMap;
import java.util.Map;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**

  • 登录控制器
    /
    @RestController
    @RequestMapping("/login")
    public class LoginController {
    /
    *
    • 主界面显示登录人
      */
      @RequestMapping(“name”)
      public Map name() {
      String name=SecurityContextHolder.getContext()
      .getAuthentication().getName(); //从session中获取当前登录的用户名(固定格式)
      Map map=new HashMap();
      // 前台可以通过loginName获取value属性
      map.put(“loginName”, name);
      return map ;
      }
      }
      前端代码
      controller.js
      app.controller(“indexController”,function ($scope,loginService){
      //展示登录名
      $scope.showLoginName=function (){
      //调用service.JS层
      loginService.loginName().success(
      //回调就是controller.java层返回来的
      function (response){
      //赋值, 页面准备通过 loginName 来获取value属性值
      KaTeX parse error: Expected 'EOF', got '}' at position 39: …e.LoginName; }̲) } }) service.…http){
      //获取用户名
      this.loginName=function (){
      return $http.get("…/login/name.do"); //调用后台的方法
      }
      })
      开始HTML回显,
      注意引入AngularJs类库和自己写的JS文件
      注意一定要让获取当前登录的用户的时间初始化就执行 ng-init
最后 需要在哪里回显就写表达式: {{loginName}} 就可以回显了

思路:

服务器端后台对象封装的分析:

  1. 对象的封装,认证通过后会返回User对象,该对象中包含用户名等信息。
  2. principal(主角)就是User对象。
  3. 框架会把principal封装到Authentication(认证)对象中
  4. Authentication对象会封装到SecurityContext(Security上下文对象)中
  5. 最后会把SecurityContext绑定到当前的线程中
    服务器编写代码??
    // 先获取到SecurityContext对象
    SecurityContext context = SecurityContextHolder.getContext();
    // 获取到认证的对象
    Authentication authentication = context.getAuthentication();
    // 获取到登录的用户信息
    User user = (User) authentication.getPrincipal();
    System.out.println(user.getUsername());
    使用EL表达式(不如框架自己表达式好)
    几乎固定的东西
    ${ sessionScope.SPRING_SECURITY_CONTEXT.authentication.principal.username }

EL表达式表面看起来是标签.但是其实后台是用一个类来运行中.jsp也是一个java程序
3.always-use-default-target=“true” />
 always-use-default-target:指定了是否在身份验证通过后总是跳转到default-target-url属性指定的URL。
访问运行商平台,想直接访问运营商里面的东西,需要先登录才跳到登录页面,但是登录完了没有跳到指定的页面default-target-url="/admin/index.html"的页面.这是一个bug
原因是我在登录之前就尝试访问了某个页面,登录成功之后就直接跳到你刚刚尝试访问的页面了,并没有去首页面.
解决思路就是添加always-use-default-target=“true” />标签

4.未登录状态下获取用户名(项目会自动分配一个默认用户名为 anonymousUser)
如果你在cartController层里面写代码获取用户名,同时你又在配置文件放行了cart那么结果就是你获取不到用户名, 同时会报错

报42行出现空指针异常

原因:
因为你做了这种释放资源的操作,它不会到Springsecurity里面去进行拦截操作,没有进入SpringSecurity里面就无法获取用户名信息.

解决问题:
解决问题方法就是在SpringSecurity.xml里面用另外一种配置来解决这个问题,同时需要注释掉放行cartController映射的那段配置

这个配置含义是:不拦截/cart/*.do请求,但是安全框架会给它分配一个匿名用户: anonymousUser
当你未登录的时候用
Stringusername= SecurityContextHolder.getContext().getAuthentication().getName();
代码获取的用户名就是 anonymousUser, 你可以用

判断是不是登录状态,如果true就是未登录状态.

(四)授权功能
当用户是普通用户的时候,当他登录系统的时候,隐藏和他角色无关的菜单,同时不允许访问和他角色无关的页面(意思就是用户手动从URL里面输入网址的时候如果他没有权限也要阻止他访问.)
1.在jsp页面使用标签控制菜单是否显示

在JSP页面中使用security:authorize标签,可以控制菜单是否显示。
security:authorize标签的access=“hasAnyRole(‘ROLE_USER’,‘ROLE_ADMIN’)”

强调:因为需要编写表达式,那么需要把表达式设置成true
Spring-security.xml:

<?xml version="1.0" encoding="UTF-8"?>

<!-- 不拦截的资源 -->
<security:http pattern="/login.jsp" security="none"/>
<security:http pattern="/failer.jsp" security="none"/>

<!-- 把一些静态资源不拦截 -->
<security:http pattern="/css/**" security="none"/>
<security:http pattern="/img/**" security="none"/>
<security:http pattern="/plugins/**" security="none"/>

<!-- 配置拦截的规则 -->
<security:http auto-config="true" use-expressions="true">
	<!-- 配置拦截的规则 -->
	<security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER','ROLE_ADMIN')"/>
	<!-- 配置自己的登录页面 -->
	<security:form-login
		login-page="/login.jsp"
		login-processing-url="/login"
		default-target-url="/index.jsp"
		authentication-failure-url="/failer.jsp"
	/>
	<!-- 关闭跨域攻击 -->
	<security:csrf disabled="true"/>
	<!-- 退出的请求地址 -->
	<security:logout logout-url="/logout" logout-success-url="/login.jsp"/>
	
	<!-- 配置权限页面 -->
	<security:access-denied-handler error-page="/403.jsp"/>
	
</security:http>

<!-- 配置密码加密类 -->
<bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>

<!-- 给框架提供数据的方式的是读取数据库中的数据 -->
<security:authentication-manager>
	<security:authentication-provider user-service-ref="sysUserService">
		<!-- 把密码的加密方式通知框架 -->
		<security:password-encoder ref="passwordEncoder"/>
	</security:authentication-provider>
</security:authentication-manager>

<!-- 服务器端授权注解开关 
<security:global-method-security jsr250-annotations="enabled"/>
-->

<!-- security框架提供注解 
<security:global-method-security secured-annotations="enabled"/>
-->

<!-- Spring框架的注解 -->
<security:global-method-security pre-post-annotations="enabled"/>

在jsp里面添加标签进行菜单控制
用: <security:authorize access=“hasAnyRole(‘ROLE_USER’,‘ROLE_ADMIN’)”>…
</security:authorize>
标签控制: hasAnyRole 括号里面的就是用户的角色, 多个角色用 ,号分割
只有 ROLE_USER和ROLE_ADMIN角色才能访问被括住的页面

 <security:authorize access="hasAnyRole('ROLE_USER','ROLE_ADMIN'

)">



  • 系统管理




    •             <li id="system-setting"><a
                          href="${pageContext.request.contextPath}/sysUser/findAllSysUser.do"> <i
                          class="fa fa-circle-o"></i> 用户管理
                  </a></li>
                  <li id="system-setting"><a
                          href="${pageContext.request.contextPath}/pages/role-list.jsp"> <i
                          class="fa fa-circle-o"></i> 角色管理
                  </a></li>
                  <li id="system-setting"><a
                          href="${pageContext.request.contextPath}/pages/permission-list.jsp">
                      <i class="fa fa-circle-o"></i> 权限管理
                  </a></li>
                  <li id="system-setting"><a
                          href="${pageContext.request.contextPath}/pages/syslog-list.jsp"> <i
                          class="fa fa-circle-o"></i> 访问日志
                  </a></li>
              </ul>
          </li>
          </security:authorize>
      

      2.阻止没权限的用户通过输入URL进入超出权限的网页
      比如,当普通用户直接从url访问管理员才能访问的用户权限,这时候就进行拦截,不让它访问

      菜单没有显示,如果直接访问地址栏,那么也会进入到具体的方法中,一定一定需要先配置AOP注解的支持,而且一定需要在springmvc.xml配置文件中配置
      在SpringMVC.xml里面配置
      1.先导入aop约束

      2.开启aop的支持

      <aop:aspectj-autoproxy proxy-target-class=“true”/> !!!注意一定要在SpringMVC.xml里面配置
      3.使用security注解方式权限拦截
      在Spring-security.xml开启注解支持 !!!注意需要约束文件支持

      <security:global-method-security secured-annotations="enabled"/>
      

      解释:
         @Secured是由Spring Security定义的用来支持方法权限控制的注解。它的使用也是需要启用对应的支持才会生效的。通过设置global-method-security元素的secured-annotations=”enabled”可以启用Spring Security对使用@Secured注解标注的方法进行权限控制的支持,其值默认为disabled。

      1.在controller类或者方法添加注解限制角色的访问
      如果是只是允许一个角色的访问就: @Secured(“ROLE_ADMIN”)
      如果是允许个角色访问就:@Secured({“ROLE_ADMIN”,“ROLE_USER”})

      列子:
      @Secured({“ROLE_ADMIN”,“ROLE_USER”})
      @Controller
      @RequestMapping("/product")
      public class ProductController {

      可以添加一个角色,也可以添加多个角色,下面是添加一个角色的:
      @Secured(“ROLE_ADMIN”)
      @Controller
      @RequestMapping("/sysUser")
      public class SYSUserController {

      3.用户访问越权的url后进行拦截
      方法1在web.xml里面配置


      403
      /403.jsp

      然后再去写403.jsp错误页面,上面路径指向的是类路径下的页面.
      方法2只能在特点的情况使用
      需要在Spring-security.xml里面配置好页面路径

      	<security:access-denied-handler error-page="/403.jsp"/>
      

      (五)数据库管理权限表的设计(不全)
      基础表: 权限表,角色表,用户表
      进行权限设计的时候最基本也需要五张(两张中间表,三张上面的基础表)

      1.权限与角色:多对多
      2.用户与角色:多对多

      SpringSocial
      (一)概念
      在OAuth协议中我们了解到了这个协议其实是一个授权协议,其目的是让用户在不将服务提供商的用户名密码提供给第三方应用的情况下,让第三方应用能够访问服务提供商的资源。

      我们在日常最常见的登录方式除了用户名密码登录,还有QQ登录、微信登录等方式。那么OAuth协议和这些登录方式是什么关系呢?

      其实就是在访问资源服务器的时候,在登录时只要拿到用户在服务提供商上边的用户信息即可。然后根据用户信息构建Authentivcation并放入SecurityContext中去。

      第三方登录实现的基本原理

      这个就是第三方登录实现的基本原理和流程。
      那么SpringSocial在这一过程中做了什么事情呢?
      SpringSocial就是将这一整个流程封装起来并且去进行实现。

      SocialAuthenticationFilter过滤器

      它把整个流程封装到了SocialAuthenticationFilter的过滤器中然后把这个过滤器加入到了SpringSecurity的过滤器链上。当访问请求的时候,SocialAuthenticationFilter会将请求拦截下来然后将整个流程走完。进而去实现第三方登录。
      SpringSocial是如何将这一流程封装到特定的接口和类中去的?

      具体的类和接口
      服务提供商相关
      在整个流程上面,从第一步到第六步,都是需要跟服务提供商打交道的。所以它的第一个接口叫–ServiceProvider,它实际上就是服务提供商的一个抽象,针对每一个服务提供商(QQ、微信),都需要一个ServiceProvider接口的一个实现。SpringSocial提供了一个AbstractOauth2ServiceProvider抽象类,实现了一些共有的东西,如果要自己写,只需要继承这个类实现其中的公有方法即可。

      回过头来再看整个流程:①第一步到第五步发放令牌其实是一个标准的流程。②到了第六步(获取用户信息)的时候其实就是一个个性化的实现,因为每一个服务提供商返回的用户信息的数据结构定义都不一样。

      针对①和②,SpringSocial提供了两个接口:
      Oauth2Operation(封装第一步到第五步)。Spring提供了一个默认的实现叫Oauth2Template,这个类会帮助我们去完成Oauth协议的执行流程。
      Api(个性化第六步),实际上没有一个明确的接口,因为每一个服务提供商对于用户基本信息的调用都是有区别的。SpringSocial其实也提供了一个抽象类叫AbstractOauth2ApiBinding帮助我们快速开发第六步的实现。
      第三方应用内部
      到了第七步实际上就跟服务提供商没有任何关系了。都是在第三方应用Client内部去完成的。
      第一个接口是Connection,SpringSocial提供的实现类叫OAuth2Connection。其作用是封装前六步执行完毕之后获取到的用户信息。
      Connection是由ConnectionFactory创建出来的,用到的类叫OAuth2ConnectionFactory。它的作用就是为了创建Connection对象获取用户信息,但是用户信息是在ServiceProvider中去构建的。所以在OAuth2ConnectionFactory中肯定有一个ServiceProvider实例,将ServiceProvider封装起来放到Connection中去。
      注意:Connection的对象名和字段名都是固定的

      之前说过,每一个服务提供商对于用户信息的定义的数据结构都是不一样的,那么ConnectionFactory是如何做到将这些不用数据结构的信息转化成对象名和字段名都是固定的Connection的对象的呢?

      ConnectionFactory中有一个ApiAdapter,将不同格式的用户信息转化为固定格式的Connection对象就是由ApiAdapter接口的实现来完成。转化成功之后就将Connection中封装进去一个用户信息。

      流程完成之后
      我们拿到了服务提供商返回给的用户信息之后,是需要将这个用户信息同步到我们自定义的数据库中去的。那么如何去实现将传过来的用户信息与我们系统中已经保存的用户信息去进行对应的呢?实际上这个对应关系是存在数据库中的。数据库中有一个用户对应表,里面有自己定义的userId以及服务提供商那边对应的用户信息的唯一标识。

      那么由谁来操纵这个表呢?就是由UsersConnectionRepository存储器去实现的。在代码中用到的实现类叫做JdbcUsersConnectionRepository,这个类的作用就是去数据库中针对用户对应关系表去做一些增删改查的操作。