【尚筹网项目】 七、【后台】 权限控制 ( 项目中加入 SpringSecurity )

权限控制


一、加入SpringSecurity的环境

(1) 依赖

在 parent 的 pom.xml 中加入

<!-- 声明属性,对SpringSecurity 的版本进行统一管理-->
<atguigu.spring.security.version>4.2.10.RELEASE</atguigu.spring.security.version>

在 parent 和 component 的 pom.xml 下都加入

<!-- SpringSecurity 对 Web 应用进行权限管理 -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>4.2.10.RELEASE</version>
</dependency>
<!-- SpringSecurity 配置 -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>4.2.10.RELEASE</version>
</dependency>
<!-- SpringSecurity 标签库 -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-taglibs</artifactId>
    <version>4.2.10.RELEASE</version>
</dependency>

(2) 在 web.xml 中配置 DelegatingFilterProxy

<!-- 配置 DelegatingFilterProxy-->
<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

注意:SpringSecurity 会根据 DelegatingFilterProxy 的 filter-name 到 IOC
容器中查找所需要的 bean。所以 filter-name 必须是 springSecurityFilterChain 名字。

(3) 创建基于注解的配置类

在这里插入图片描述

// 表示当前类是一个配置类
@Configuration

// 启用 Web环境下权限配置功能
@EnableWebSecurity
public class WebAppSecurityConfig extends WebSecurityConfigurerAdapter {

	@Override
    protected void configure(AuthenticationManagerBuilder builder) throws Exception {

    }

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

    }
}

(4) 谁来把 WebAppSecurityConfig 扫描到 IOC 里?

在这里插入图片描述
在这里插入图片描述

结论:为了让 SpringSecurity 能够针对浏览器请求进行权限控制,需要让
SpringMVC 来扫描 WebAppSecurityConfig 类。

衍生问题:DelegatingFilterProxy 初始化时需要到 IOC 容器查找一个 bean,
这个 bean 所在的 IOC 容器要看是谁扫描了 WebAppSecurityConfig。
如果是 Spring 扫描了 WebAppSecurityConfig,那么 Filter 需要的 bean 就在
Spring 的 IOC 容器。

如果是 SpringMVC 扫描了 WebAppSecurityConfig,那么 Filter 需要的 bean
就在 SpringMVC 的 IOC 容器。

(5) 找不到 bean 的问题分析

① 明确三大组件启动顺序
首先:ContextLoaderListener 初始化,创建 Spring 的 IOC 容器

其次:DelegatingFilterProxy 初始化,查找 IOC 容器、查找 bean

最后:DispatcherServlet 初始化,创建 SpringMVC 的 IOC 容器
② DelegatingFilterProxy 查找 IOC 容器然后查找 bean 的工作机制

在这里插入图片描述

③ 解决方案:改源码

改源码时,创建的类要和源码里的类有相同的包,相同目录路径,然后将源码中代码粘贴进来,最后再对源码进行修改
在这里插入图片描述

修改两处:

在这里插入图片描述

在这里插入图片描述

// 把原来的查找 IOC 容器的代码注释掉
// WebApplicationContext wac = findWebApplicationContext();

// 按我们自己的需要重新编写
// 1.获取 ServletContext 对象
ServletContext sc = this.getServletContext();

// 2.拼接 SpringMVC 将 IOC 容器存入 ServletContext 域的时候使用的属性名
String servletName = "springDispatcherServlet";
String attrName = FrameworkServlet.SERVLET_CONTEXT_PREFIX + servletName;

// 3.根据 attrName 从 ServletContext 域中获取 IOC 容器对象
WebApplicationContext wac = (WebApplicationContext) sc.getAttribute(attrName);

二、目标 1:放行登录页和静态资源

在这里插入图片描述

security
       .authorizeRequests() //对请求进行授权
       .antMatchers("/admin/to/login/page.html") // 允许访问登录页
       .permitAll() // 无条件访问
       .antMatchers("/bootstrap/**") // 针对静态资源进行设置,无条件访问
       .permitAll() // 针对静态资源进行设置,无条件访问
       .antMatchers("/crowd/**") // 针对静态资源进行设置,无条件访问
       .permitAll() // 针对静态资源进行设置,无条件访问
       .antMatchers("/css/**") // 针对静态资源进行设置,无条件访问
       .permitAll() // 针对静态资源进行设置,无条件访问
       .antMatchers("/fonts/**") // 针对静态资源进行设置,无条件访问
       .permitAll() // 针对静态资源进行设置,无条件访问
       .antMatchers("/img/**") // 针对静态资源进行设置,无条件访问
       .permitAll() // 针对静态资源进行设置,无条件访问
       .antMatchers("/jquery/**") // 针对静态资源进行设置,无条件访问
       .permitAll() // 针对静态资源进行设置,无条件访问
       .antMatchers("/bootstrap/**") // 针对静态资源进行设置,无条件访问
       .permitAll() // 针对静态资源进行设置,无条件访问
       .antMatchers("/crowd/**") // 针对静态资源进行设置,无条件访问
       .permitAll() // 针对静态资源进行设置,无条件访问
       .antMatchers("/css/**") // 针对静态资源进行设置,无条件访问
       .permitAll() // 针对静态资源进行设置,无条件访问
       .antMatchers("/fonts/**") // 针对静态资源进行设置,无条件访问
       .permitAll() // 针对静态资源进行设置,无条件访问
       .antMatchers("/img/**") // 针对静态资源进行设置,无条件访问
       .permitAll() // 针对静态资源进行设置,无条件访问
       .antMatchers("/jquery/**") // 针对静态资源进行设置,无条件访问
       .permitAll() // 针对静态资源进行设置,无条件访问
       ;

三、目标 2:提交登录表单做内存认证

(1) 思路

在这里插入图片描述

(2) 设置表单 admin-login.jsp

在这里插入图片描述

在这里插入图片描述

security/do/login.html

<p>${SPRING_SECURITY_LAST_EXCEPTION.message }</p>

(3) SpringSecurity 配置

在这里插入图片描述

.anyRequest().authenticated() //  其他任意请求 --- 认证后访问
.and()
.csrf().disable() // 防跨站请求伪造功能 --- 禁用
.formLogin() // 开启表单登录的功能
.loginPage("/admin/to/login/page.html") // 指定登录页面
.loginProcessingUrl("/security/do/login.html") // 指定处理登录请求的地址
.defaultSuccessUrl("/admin/to/main/page.html")// 指定登录成功后前往的地址
.usernameParameter("loginAcct") // 账号的请求参数名称
.passwordParameter("userPswd") // 密码的请求参数名称

在这里插入图片描述

// 临时使用内存版登录的模式测试代码
builder.inMemoryAuthentication().withUser("tom").password("123123").roles("Admin");

(4) 取消以前的自定义登录拦截器

在这里插入图片描述

在这里插入图片描述

四、目标 3:退出登录

在这里插入图片描述

.and()
.logout()                                       // 开启退出登录功能
.logoutUrl("/security/do/logout.html")          //指定退出登录地址
.logoutSuccessUrl("/admin/to/login/page.html")  //指定退出登录成功以后访问的地址

五、目标4:把内存登录改成数据库登录

(1) 思路

在这里插入图片描述

(2) 根据 adminId 查询已分配的角色 (之前写了)

在这里插入图片描述

(3) 根据 adminId 查询已分配权限

① AuhtService

在这里插入图片描述

List<String> getAssignedAuthNameByAdminId(Integer adminId);
② AuthServiceImpl
@Override
public List<String> getAssignedAuthNameByAdminId(Integer adminId) {
    return authMapper.selectAssignedAuthNameByAdminId(adminId);
}
③ AuthMapper

在这里插入图片描述

List<String> selectAssignedAuthNameByAdminId(Integer adminId);
④ AuthMapper.xml

在这里插入图片描述

<select id="selectAssignedAuthNameByAdminId" resultType="string">
    SELECT DISTINCT t_auth.name
    FROM t_auth
    LEFT JOIN inner_role_auth ON t_auth.id=inner_role_auth.auth_id
    LEFT JOIN inner_admin_role ON inner_admin_role.role_id=inner_role_auth.role_id
    WHERE inner_admin_role.admin_id=#{adminId} and t_auth.name != "" and t_auth.name is
    not null
</select>

(4) 创建 SecurityAdmin 类

在这里插入图片描述

/**
 *  考虑到 User 对象中仅仅包含账号和密码,为了能够获取到原始的 Admin 对象,专门创建
 * 这个类对 User 类进行扩展
 */
public class SecurityAdmin extends User {

    private static final long serialVersionUID = 1L;
    // 原始的 Admin 对象,包含 Admin 对象的全部属性
    private Admin originalAdmin;
    public SecurityAdmin(
            // 传入原始的 Admin 对象
            Admin originalAdmin,
            // 创建角色、权限信息的集合
            List<GrantedAuthority> authorities) {

        // 调用父类构造器
        super(originalAdmin.getLoginAcct(), originalAdmin.getUserPswd(), authorities);

        // 给本类的 this.originalAdmin 赋值
        this.originalAdmin = originalAdmin;
    }

    // 对外提供的获取原始 Admin 对象的 getXxx()方法
    public Admin getOriginalAdmin() {
        return originalAdmin;
    }
}

(5) 根据账号查询 Admin

在这里插入图片描述

@Override
public Admin getAdminByLoginAcct(String username) {
    AdminExample example = new AdminExample();
    Criteria criteria = example.createCriteria();
    criteria.andLoginAcctEqualTo(username);
    List<Admin> list = adminMapper.selectByExample(example);
    Admin admin = list.get(0);
    return admin;
}

(6) 创建 UserDetailsService 实现类

在这里插入图片描述

@Component
public class CrowdUserDetailsService implements UserDetailsService {
    @Autowired
    private AdminService adminService;

    @Autowired
    private RoleService roleService;

    @Autowired
    private AuthService authService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        // 1.根据账号名称查询 Admin 对象
        Admin admin = adminService.getAdminByLoginAcct(username);

        // 2.获取 adminId
        Integer adminId = admin.getId();

        // 3.根据 adminId 查询角色信息
        List<Role> assignedRoleList = roleService.getAssignedRole(adminId);

        // 4.根据 adminId 查询权限信息
        List<String> authNameList = authService.getAssignedAuthNameByAdminId(adminId);

        // 5.创建集合对象用来存储 GrantedAuthority
        // 存储角色和权限的信息,存在一起
        List<GrantedAuthority> authorities = new ArrayList<>();

        // 6.遍历 assignedRoleList 存入角色信息
        for (Role role : assignedRoleList) {
            // 注意:不要忘了加前缀!
            String roleName = "ROLE_" + role.getName();
            SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(roleName);
            authorities.add(simpleGrantedAuthority);
        }

        // 7.遍历 authNameList 存入权限信息
        for (String authName : authNameList) {
            SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(authName);
            authorities.add(simpleGrantedAuthority);
        }

        // 8.封装 SecurityAdmin 对象
        SecurityAdmin securityAdmin = new SecurityAdmin(admin, authorities);
        return securityAdmin;
    }
}

(7) 在配置类中使用 UserDetailsService

在这里插入图片描述

@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
    // 临时使用内存版登录的模式测试代码
    // builder.inMemoryAuthentication().withUser("tom").password("123123").roles("Admin");

    // 正式功能中使用基于数据库的认证
    builder.userDetailsService(userDetailsService);
}

六、目标5:密码加密

(1) 修改 t_admin 表结构

修改的原因:以前使用 JDK 自带的 MessageDigest 进行加密操作,生成的密文长度为 32。现在使用带盐值的加密方式,生成的密文长度超过这个数值,所以要
修改。

ALTER TABLE `project_crowd`.`t_admin` CHANGE `user_pswd` `user_pswd` CHAR(100) CHARSET
utf8 COLLATE utf8_general_ci NOT NULL;

在这里插入图片描述

(2) 准备 BCryptPasswordEncoder 对象

在这里插入图片描述

<!--  配置 BCryptPasswordEncoder,不在WebAppSecurityConfig中使用@bean的原因
     是:在那个类中配置的bean加入到的是SpringMVC的Ioc容器中,而使用时的xxxServiceImpl中
     是从Spring的IOC容器中取,因此拿不到bean
-->
<bean id="BCryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder">
</bean>

(3) 使用 BCryptPasswordEncoder 对象

在这里插入图片描述

@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
// 正式功能中使用基于数据库的认证
builder.userDetailsService(userDetailsService)
       .passwordEncoder(bCryptPasswordEncoder); //解密

(4) 使用 BCryptPasswordEncoder 在保存 Admin 时加密

AdminServiceImpl
在这里插入图片描述

// 使用SpringSecurity的 bCryptPasswordEncoder 进行加密
String encoded  = bCryptPasswordEncoder.encode(userPswd);

测试 (添加tom) :
在这里插入图片描述


七、目标 6:在页面上显示用户昵称

(1) 导入标签库

在这里插入图片描述

<%@ taglib uri="http://www.springframework.org/security/tags" prefix="security" %>

(2) 通过标签获取已登录用户信息

在这里插入图片描述

<security:authentication property="principal.originalAdmin.userName" />

在这里插入图片描述

八、目标 7:密码的擦除

擦除密码是在不影响登录认证的情况下,避免密码泄露,增强系统的安全性
在这里插入图片描述

// 将原始 Admin 对象中的密码擦除
this.originalAdmin.setUserPswd(null);

九、目标 8:权限控制

(1) 设置测试数据

在这里插入图片描述

(2) 测试一:要求:访问 Admin 分页功能时具备“经理”角色

在这里插入图片描述

.antMatchers("/admin/get/page.html") // 访问 Admin 分页功能时具备“经理”角色
.hasRole("经理")

在这里插入图片描述

在这里插入图片描述

.and()
.exceptionHandling()                            //指定处理拒绝访问的controller
.accessDeniedHandler(new AccessDeniedHandler(){
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        request.setAttribute("exception",new Exception(CrowdConstant.MESSAGE_ACCESS_DENIED));

        request.getRequestDispatcher("/WEB-INF/system-error.jsp").forward(request, response);
    }
})

(3) 测试二:要求:访问 Role 的分页功能时具备“部长”角色

在这里插入图片描述

@PreAuthorize("hasRole('部长')")

在这里插入图片描述

@EnableGlobalMethodSecurity(prePostEnabled = true)

(4) 测试三:要求:访问 Admin 保存功能时具备 user:save 权限

在这里插入图片描述

可以先把这个注释掉,让 roleOperator 和 adminOperator都能进入admin-page,然后测试是否可以保存

在这里插入图片描述

测试结果:adminOperator可以保存,其他则不能保存


(5) 测试四:要求:访问 Admin 分页功能时具备“经理”角色或“user:get”权限二者之一

在这里插入图片描述

.antMatchers("/admin/get/page.html") // 访问 Admin 分页功能时需要具备的角色和权限
.access("hasRole('经理') or hasAuthority('user:get')")  要 求 具 备 “ 经 理 ” 角 色 和“user:get”权限二者之一

测试结果:adminOperator 和 roleOperator 都可以进入 admin-page


十、目标 9:页面元素的权限控制

(1) 要求

页面上的局部元素,根据访问控制规则进行控制。

(2) 标签库

在这里插入图片描述

<security:authorize access="hasRole('经理')"></security:authorize>

(3) 异常 No visible WebSecurityExpressionHandler instance could be found

原因:AbstractAuthorizeTag 类默认是查找“根级别”的 IOC 容器。而根级别的IOC 容器中没有扫描 SpringSecurity 的配置类,所以没有相关的 bean。

解决: 修改源码

在这里插入图片描述

//ApplicationContext appContext = SecurityWebApplicationContextUtils.findRequiredWebApplicationContext(getServletContext());

// 1.获取 ServletContext 对象
ServletContext servletContext = getServletContext();

// 2.拼接 SpringMVC 在 ServletContext 域中的属性名
String attrName = FrameworkServlet.SERVLET_CONTEXT_PREFIX + "springDispatcherServlet";

// 3.从 ServletContext 域中获取 IOC 容器对象
ApplicationContext appContext = (ApplicationContext) servletContext.getAttribute(attrName);
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值