• 尚好房 10_Spring Security


    尚好房:Spring Security

    一、Spring Security概念

    前面我们已经完成了尚好房权限管理的部分相关功能,给用户分配角色,给角色分配权限,及左侧动态菜单,做好权限管理的数据准备,接下来我们要使用这些数据进行权限的相关控制。

    1、认证和授权概念

    现在我们需要思考2个问题:

    *问题1*:在生产环境下我们如果不登录后台系统就可以完成这些功能操作吗?

    答案显然是否定的,要操作这些功能必须首先登录到系统才可以。

    *问题2*:是不是所有用户,只要登录成功就都可以操作所有功能呢?

    答案是否定的,并不是所有的用户都可以操作这些功能。不同的用户可能拥有不同的权限,这就需要进行授权了。(用户登录之后,对每个用户进行授权,通过授权去访问系统中不同的功能–>授权)

    *认证*:系统提供的用于识别用户身份的功能,通常提供用户名和密码进行登录其实就是在进行认证,认证的目的是让系统知道你是谁。

    *授权*:用户认证成功后,需要为用户授权,其实就是指定当前用户可以操作哪些功能。

    本章节就是要对后台系统进行权限控制,其本质就是对用户进行认证和授权。

    2、Spring Security简介

    Spring Security是 Spring提供的安全认证服务的框架。 使用Spring Security可以帮助我们来简化认证和授权的过程。

    官网:https://spring.io/projects/spring-security/

    中文官网:https://www.w3cschool.cn/springsecurity/

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p1BH9U44-1661871891695)(images/10/img_001.png)]

    对应的maven坐标:

    
    <dependency>
        <groupId>org.springframework.securitygroupId>
        <artifactId>spring-security-webartifactId>
        <version>5.2.7.RELEASEversion>
    dependency>
    <dependency>
        <groupId>org.springframework.securitygroupId>
        <artifactId>spring-security-configartifactId>
        <version>5.2.7.RELEASEversion>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    常用的权限框架除了Spring Security,还有Apache的shiro框架。

    二、Spring Security集成入门

    目标: 让用户访问管理后台中的资源的时候,需要输入用户名和密码进行登录

    1、引入依赖

    1.1、shf-parent添加版本管理

    pom.xml

    <spring.security.version>5.2.7.RELEASEspring.security.version>
    
    • 1
    
    <dependency>
        <groupId>org.springframework.securitygroupId>
        <artifactId>spring-security-webartifactId>
        <version>${spring.security.version}version>
    dependency>
    <dependency>
        <groupId>org.springframework.securitygroupId>
        <artifactId>spring-security-configartifactId>
        <version>${spring.security.version}version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    1.2、web-admin引入依赖

    目前只是我们的后台管理系统需要授权与认证

    pom.xml

    <dependencies>
        
        <dependency>
            <groupId>org.springframework.securitygroupId>
            <artifactId>spring-security-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.securitygroupId>
            <artifactId>spring-security-configartifactId>
        dependency>
    dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2、配置Spring Security Fillter

    web.xml

    
    
    <filter>
      <filter-name>springSecurityFilterChainfilter-name>
      <filter-class>org.springframework.web.filter.DelegatingFilterProxyfilter-class>
    filter>
    <filter-mapping>
      <filter-name>springSecurityFilterChainfilter-name>
      <url-pattern>/*url-pattern>
    filter-mapping>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3、配置Spring Security

    配置Spring Security有两种方式:

    ​ 1、xml文件配置

    ​ 2、java类配置

    两种方式配置效果一致,当前我们使用java类配置,更加简洁,我们在web-admin项目中创建com.atguigu.config.WebSecurityConfig

    package com.atguigu.config;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    
    
    @Configuration
    @EnableWebSecurity //@EnableWebSecurity是开启SpringSecurity的默认行为
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    4、测试

    仅需三个步骤,我们就已经集成好了Spring Security,其他的事情就可以交给Spring Security为我们处理。

    启动项目

    访问:http://localhost:8000/

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bspeZ7xs-1661871891700)(images/10/img_002.png)]

    所有资源访问受限(包括静态资源)

    url自动跳转到了一个默认的登录页面(框架自带的),我们目前没有定义login页面及login controller方法。

    但是当前没有账号啊!下面我们测试一个最简单的内存分配用户名密码。

    4.1、内存分配用户名密码

    操作类:WebSecurityConfig

    重写configure(AuthenticationManagerBuilder auth)方法

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("lucy")
                .password(new BCryptPasswordEncoder().encode("123456"))
                .roles("");
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    请求:http://localhost:8000/

    报错:springsecurity There is no PasswordEncoder mapped for the id "null"

    需要设置加密方式

    4.2、设置加密方式
    /**
     * 必须指定加密方式,上下加密方式要一致
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    登录成功,但是iframe部分页面不显示

    4.3、设置允许iframe嵌套显示

    默认Spring Security不允许iframe嵌套显示,我们需要设置

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //必须调用父类的方法,否则就不需要认证即可访问
        super.configure(http);
        //允许iframe嵌套显示
        http.headers().frameOptions().disable();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    到目前为止,我们通过内存分配用户名密码的方式,可以访问后台页面了。

    三、Spring Security集成进阶

    前面我们已经完成了Spring Security的入门级配置,通过Spring Security的使用,Spring Security将我们项目中的所有资源都保护了起来,要访问这些资源必须要完成认证才能够访问。

    但是这个案例中的使用方法离我们真实生产环境还差很远,还存在如下一些问题:

    1、项目中我们将所有的资源(所有请求URL)都保护起来,实际环境下往往有一些资源不需要认证也可以访问,也就是可以匿名访问。

    2、登录页面是由框架生成的,而我们的项目往往会使用自己的登录页面。

    3、直接将用户名和密码配置在了java程序中,而真实生产环境下的用户名和密码往往保存在数据库中。

    现在我们需要对这些问题进行改进。

    1. 自定义登录页面

    1.1 创建登录页面

    ① 在web-admin项目中创建templates/frame/login.html页面

    DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    
        <head th:include="common/head :: head">head>
    
        <body class="gray-bg">
    
            <div class="middle-box text-center loginscreen  animated fadeInDown">
                <div>
                    <div>
    
                        <h1 class="logo-name">h1>
    
                    div>
                    <h3>欢迎使用 尚好房平台管理系统h3>
    
                    <form class="m-t" role="form" th:action="@{/login}" method="post">
                        <label style="color:red;" th:if="${param.error}" th:text="用户名或密码错误">label>
                        <div class="form-group">
                            <input type="text" name="username" value="admin" class="form-control" placeholder="用户名" required="">
                        div>
                        <div class="form-group">
                            <input type="password" name="password" value="123456" class="form-control" placeholder="密码" required="">
                        div>
                        <button type="submit" class="btn btn-primary block full-width m-b">登 录button>
    
    
                        <p class="text-muted text-center"> <a href="javascript:"><small>忘记密码了?small>a> | <a href="javascript:">注册一个新账号a>
                        p>
                    form>
                div>
            div>
        body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    ② 在spring-mvc.xml中配置访问登录页面的请求映射

    <mvc:view-controller path="/login" view-name="frame/login"/>
    
    • 1
    1.2 修改WebSecurityConfig配置类

    在WebSecurityConfig配置类中重写如下方法:

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //允许iframe嵌套显示
        http.headers().frameOptions().disable();
        http
            .authorizeRequests()
            .antMatchers("/static/**","/login").permitAll()  //允许匿名用户访问的路径
            .anyRequest().authenticated()    // 其它页面全部需要验证
            .and()
            .formLogin()
            .loginPage("/login")    //用户未登录时,访问任何需要权限的资源都转跳到该路径,即登录页面,此时登陆成功后会继续跳转到第一次访问的资源页面(相当于被过滤了一下)
            .defaultSuccessUrl("/") //登录认证成功后默认转跳的路径
            .and()
            .logout()
            .logoutUrl("/logout")   //退出登陆的路径,指定spring security拦截的注销url,退出功能是security提供的
            .logoutSuccessUrl("/login");//用户退出后要被重定向的url
        //关闭跨域请求伪造
        http.csrf().disable();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    2. 使用数据库表中的用户名和密码

    2.1 注释掉内存分配用户名和密码
    /*@Override
      protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication()
                    .withUser("lucy")
                    .password(new BCryptPasswordEncoder().encode("123456"))
                    .roles("");
    }*/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    2.2 持久层
    2.2.1 AdminMapper接口

    service-acl项目的com.atguigu.mapper.AdminMapper接口中新增方法

    Admin getByUsername(String username);
    
    • 1
    2.2.2 AdminMapper.xml映射配置文件

    service-acl项目的resources/mappers/AdminMapper.xml中新增

    <select id="getByUsername" resultType="Admin">
        <include refid="columns">include>
        from acl_admin
        where
        username = #{username}
        and is_deleted = 0
    select>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    2.3 业务层
    2.3.1 AdminService接口

    service-api项目的com.atguigu.service.AdminService接口中新增方法

    Admin getByUsername(String username);
    
    • 1
    2.3.2 AdminServiceImpl实现类

    service-acl项目的com.atguigu.service.impl.AdminServiceImpl实现类中新增方法

    @Override
    public Admin getByUsername(String username) {
        return adminMapper.getByUsername(username);
    }
    
    • 1
    • 2
    • 3
    • 4
    2.4 UserDetailsService接口的实现类

    Spring Security支持通过实现UserDetailsService接口的方式来提供用户认证授权信息

    2.4.1 新建UserDetailsServiceImpl实现类

    我们在web-admin项目中创建com.atguigu.config.UserDetailsServiceImpl实现类

    @Component
    public class UserDetailsServiceImpl implements UserDetailsService {
    
        @Reference
        private AdminService adminService;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            //根据用户名查找用户
            Admin admin =adminService.getByUsername(username);
            if(null == admin) {
                throw new UsernameNotFoundException("用户名不存在!");
            }
            //校验密码,操作权限目前用空的
            return new User(username,admin.getPassword(),
                    AuthorityUtils.commaSeparatedStringToAuthorityList(""));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    2.4.2 添加用户时对密码进行加密

    前面添加用户是我们没有对密码进行加密处理,现在改造。删除未加密的数据记录,重新创建用户信息

    修改com.atguigu.controller.AdminControllersave方法

    @Autowired
    private PasswordEncoder passwordEncoder;
    @PostMapping("/save")
    public String save(Admin admin, Model model){
        //设置密码
        admin.setPassword(passwordEncoder.encode(admin.getPassword()));
        adminService.insert(admin);
        return successPage(model,"新增用户成功");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    3. 左侧动态菜单

    之前我们获取左侧动态菜单的时候,是写死用户为admin,现在可以用Spring Security获取登录的用户

    修改web-admin项目中的com.atguigu.controller.IndexController类的index()方法

    @GetMapping("/")
    public String index(Model model){
        //获取当前登录的用户
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        User user = (User) authentication.getPrincipal();
        Admin admin = adminService.getByUsername(user.getUsername());
        //查询用户的权限列表
        List<Permission> permissionList = permissionService.findMenuPermissionByAdminId(admin.getId());
        model.addAttribute("admin",admin);
        model.addAttribute("permissionList",permissionList);
        return PAGE_INDEX;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    4. 用户授权

    4.1 获取用户权限
    4.1.1 持久层
    4.1.1.1 PermissionMapper接口

    service-acl项目的com.atguigu.mapper.PermissionMapper接口中新增

    /**
    * 查询用户的操作权限
    * @param adminId
    * @return
    */
    List<String> findCodePermissionListByAdminId(Long adminId);
    
    /**
    * 查询所有操作权限
    * @return
    */
    List<String> findAllCodePermission();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    4.1.1.2 PermissionMapper.xml映射配置文件

    service-acl项目中的resources/mappers/PermissionMapper.xml中新增

    <select id="findAllCodePermission" resultType="string">
        select code from acl_permission where is_deleted=0
    select>
    
    <select id="findCodePermissionListByAdminId" resultType="string">
        SELECT code
        FROM acl_permission
        WHERE id IN (
        SELECT permission_id FROM acl_role_permission WHERE role_id IN (
        SELECT role_id FROM acl_admin_role WHERE admin_id=#{adminId} AND is_deleted=0
        ) AND is_deleted=0
        ) AND TYPE=2 AND is_deleted=0
    select>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    4.1.2 业务层
    4.1.2.1 PermissionService接口

    service-api项目的com.atguigu.service.PermissionService接口中新增

    /**
    * 查询用户的操作权限
    * @param adminId
    * @return
    */
    List<String> findCodePermissionListByAdminId(Long adminId);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    4.1.2.2 PermissionServiceImpl实现类

    service-acl项目的com.atguigu.service.impl.PermissionServiceImpl实现类中新增

    @Override
    public List<String> findCodePermissionListByAdminId(Long adminId) {
        //判断是否是超级管理员
        if (adminId == 1) {
            //拥有所有权限
            return permissionMapper.findAllCodePermission();
        }
        return permissionMapper.findCodePermissionListByAdminId(adminId);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    4.2 将用户权限配置到Spring Security中

    修改web-admin项目的com.atguigu.config.UserDetailsServiceImpl

    @Component
    public class UserDetailServiceImpl implements UserDetailsService {
        @Reference
        private AdminService adminService;
        @Reference
        protected PermissionService permissionService;
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            Admin admin = adminService.getByUsername(username);
            if(null == admin) {
                throw new UsernameNotFoundException("用户名不存在!");
            }
            //获取用户权限列表
            List<String> codePermissionList = permissionService.findCodePermissionListByAdminId(admin.getId());
    
            List<GrantedAuthority> grantedAuthorityList = new ArrayList<>();
            for (String code : codePermissionList) {
                if(StringUtils.isEmpty(code)) {
                    continue;
                }
                grantedAuthorityList.add(new SimpleGrantedAuthority(code));
            }
            return new User(username,admin.getPassword(), grantedAuthorityList);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    4.3 Controller方法权限控制

    **目标:**给各个Controller的方法指定对应的操作权限,以角色管理增删改查等为例

    4.3.1 开启Controller方法权限控制

    修改web-admin项目中的com.atguigu.config.WebSecurityConfig配置类,添加下述注解

    @EnableGlobalMethodSecurity(prePostEnabled = true)
    
    • 1
    4.3.2 给Controller方法添加权限注解

    修改web-admin项目中的com.atguigu.controller.RoleController

    @Controller
    @RequestMapping("/role")
    public class RoleController extends BaseController {
        @Reference
        private RoleService roleService;
        @Reference
        private PermissionService permissionService;
        private final static String LIST_ACTION = "redirect:/role";
        private static final String PAGE_ASSIGN_SHOW = "role/assignShow";
    
        @PreAuthorize("hasAnyAuthority('role.show')")
        @RequestMapping
        public String index(@RequestParam Map conditions, Model model){
            if(!conditions.containsKey("pageNum")) {
                conditions.put("pageNum", 1);
            }
            if(!conditions.containsKey("pageSize")) {
                conditions.put("pageSize", 10);
            }
    
            PageInfo<Role> pageInfo = roleService.findPage(conditions);
            model.addAttribute("page", pageInfo);
            model.addAttribute("conditions", conditions);
            return "role/index";
        }
    
        @PreAuthorize("hasAnyAuthority('role.create')")
        @PostMapping("/saveRole")
        public String saveRole(Role role, Model model){
            roleService.insert(role);
            return successPage(model,"添加角色成功");
        }
    
        @PreAuthorize("hasAnyAuthority('role.show')")
        @GetMapping("/findById/{id}")
        public String findRoleById(@PathVariable("id") Long id,Model model){
            Role role = roleService.getById(id);
            model.addAttribute("role",role);
            return "role/edit";
        }
    
        @PreAuthorize("hasAnyAuthority('role.edit')")
        @PostMapping("/updateRole")
        public String updateRole(Role role,Model model){
            roleService.update(role);
            return successPage(model,"更新角色成功");
        }
    
        @PreAuthorize("hasAnyAuthority('role.delete')")
        @GetMapping("/delete/{id}")
        public String deleteRoleById(@PathVariable("id") Long id){
            roleService.delete(id);
            return LIST_ACTION;
        }
    
        @PreAuthorize("hasAnyAuthority('role.assgin')")
        @GetMapping("/assignShow/{roleId}")
        public String assignShow(@PathVariable("roleId") Long roleId,Model model){
            List<Map<String, Object>> zNodes = permissionService.findPermissionByRoleId(roleId);
            model.addAttribute("zNodes", JSON.toJSONString(zNodes));
            model.addAttribute("roleId",roleId);
            return PAGE_ASSIGN_SHOW;
        }
    
        @PreAuthorize("hasAnyAuthority('role.assgin')")
        @PostMapping("/assignPermission")
        public String assignPermission(@RequestParam("roleId") Long roleId,
                                       @RequestParam("permissionIds") List<Long> permissionIds,
                                       Model model){
            permissionService.saveRolePermission(roleId,permissionIds);
            return successPage(model,"设置角色权限成功");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    4.4 测试

    目前已admin登录,这些权限都有,点击角色管理相关功能都能正常访问。使用Admin设置某个用户只有查看角色的权限

    然后admin退出登录,使用只有查看角色权限的用户登录,观察操作效果

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b5Oi2V8T-1661871891703)(images/10/img_003.png)]

    不能访问了,提示403错误状态,表示没有访问权限。

    4.5 优化用户体验

    上面这样提示很不友好,我们自定义提示页面

    4.5.1 创建提示页面

    web-admin项目中创建templates/frame/auth.html

    DOCTYPE html>
    <html>
        <head>
        head>
        <body style="position: relative;">
            <div style="text-align:center;margin-top: 100px;font-size: 20px;">
                <strong>没有权限strong>
            div>
        body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    4.5.2 在spring-mvc.xml中配置访问提示页面

    web-admin项目中的resources/spring/spring-mvc.xml中新增

    
    <mvc:view-controller path="/auth" view-name="frame/auth"/>
    
    • 1
    • 2
    4.5.3 实现AccessDeniedHandler接口

    web-admin项目中创建com.atguigu.config.AtguiguAccessDeniedHandler

    public class AtguiguAccessDeniedHandler implements AccessDeniedHandler {
    
        @Override
        public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
            httpServletResponse.sendRedirect("/auth");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    4.5.4 在Spring Security配置类中配置AtguiguAccessDeniedHandler

    修改web-admin项目中的com.atguigu.config.WebSecurityConfig

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //允许iframe嵌套显示
        http.headers().frameOptions().disable();
        http
            .authorizeRequests()
            .antMatchers("/static/**","/login").permitAll()  //允许匿名用户访问的路径
            .anyRequest().authenticated()    // 其它页面全部需要验证
            .and()
            .formLogin()
            .loginPage("/login")    //用户未登录时,访问任何需要权限的资源都转跳到该路径,即登录页面,此时登陆成功后会继续跳转到第一次访问的资源页面(相当于被过滤了一下)
            .defaultSuccessUrl("/") //登录认证成功后默认转跳的路径,意思时admin登录后也跳转到/user
            .and()
            .logout()
            .logoutUrl("/logout")   //退出登陆的路径,指定spring security拦截的注销url,退出功能是security提供的
            .logoutSuccessUrl("/login");//用户退出后要被重定向的url
        //关闭跨域请求伪造
        http.csrf().disable();
    	
        //指定自定义的访问拒绝处理器
        http.exceptionHandling().accessDeniedHandler(new AtguiguAccessDeniedHandler());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    4.6 优化之后的测试

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-etCivD6l-1661871891705)(images/10/img_004.png)]

    5. 页面功能按钮权限控制

    上面我们完成了controller层方法的权限,现在我们要控制页面按钮的权限,如:角色管理上面只有查看权限,那么页面新增、修改、删除、分配权限按都不显示。

    怎么实现呢?其实Spring Security已经给我们封装好了标签库,我们直接使用即可。

    5.1 shf-parent工程管理依赖版本

    pom.xml

    <thymeleaf-springsecurity5.version>3.0.4.RELEASEthymeleaf-springsecurity5.version>
    
    • 1
    
    <dependency>
        <groupId>org.thymeleaf.extrasgroupId>
        <artifactId>thymeleaf-extras-springsecurity5artifactId>
        <version>${thymeleaf-springsecurity5.version}version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    5.2 web-admin引入依赖
    
    <dependency>
        <groupId>org.thymeleaf.extrasgroupId>
        <artifactId>thymeleaf-extras-springsecurity5artifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    5.3 Thymeleaf模板引擎配置spring security 标签支持

    修改web-admin项目的resources/spring/spring-mvc.xml配置文件,在Thymeleaf的模板引擎配置spring security 标签支持

    
    <bean id="templateEngine" class="org.thymeleaf.spring5.SpringTemplateEngine">
        
        <property name="templateResolver" ref="templateResolver">property>
        
        <property name="additionalDialects">
            <set>
                <bean class="org.thymeleaf.extras.springsecurity5.dialect.SpringSecurityDialect" />
            set>
        property>
    bean>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    5.4 页面按钮控制

    ① 在html文件里面声明使用spring-security标签

    <html xmlns:th="http://www.thymeleaf.org"
          xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
    
    • 1
    • 2

    ② 在相关按钮上使用标签

    <button type="button" class="btn btn-sm btn-primary create" sec:authorize="hasAuthority('role.create')">新增button>
    
    <a class="edit" th:attr="data-id=${item.id}" sec:authorize="hasAuthority('role.edit')">修改a>
    <a class="delete" th:attr="data-id=${item.id}" sec:authorize="hasAuthority('role.delete')">删除a>
    <a class="assgin" th:attr="data-id=${item.id}" sec:authorize="hasAuthority('role.assgin')">分配权限a>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    ③ 页面完整代码

    DOCTYPE html>
    <html lang="en"
          xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
          xmlns:th="http://www.thymeleaf.org">
        <head th:include="common/head :: head">head>
        <body class="gray-bg">
            <form id="ec" th:action="@{/role}" method="get">
                <div class="wrapper wrapper-content animated fadeInRight">
                    <div class="row">
                        <div class="col-sm-12">
                            <div class="ibox float-e-margins">
                                <div class="ibox-content">
                                    <table class="table form-table margin-bottom10">
                                        <tr>
                                            <td>
                                                <input type="text" name="roleName" th:value="${#maps.containsKey(conditions, 'roleName')} ? ${conditions.roleName} : ''" placeholder="角色名称" class="input-sm form-control"/>
                                            td>
                                        tr>
                                    table>
                                    <div>
                                        <button type="button" class="btn btn-sm btn-primary" onclick="javascript:document.forms.ec.pageNum.value=1;document.forms.ec.submit();">搜索button>
                                        <button type="button" onclick="addRole()"
                                                sec:authorize="hasAuthority('role.create')"
                                                class="btn btn-sm btn-primary">新增button>
                                        <button type="button" id="loading-example-btn" onclick="javascript:window.location.reload();" class="btn btn-white btn-sm">刷新button>
                                    div>
                                    <table class="table table-striped table-bordered table-hover dataTables-example">
                                        <thead>
                                            <tr>
                                                <th>序号th>
                                                <th>角色名称th>
                                                <th>角色编码th>
                                                <th>描述th>
                                                <th>创建时间th>
                                                <th>操作 th>
                                            tr>
                                        thead>
                                        <tbody>
                                            <tr class="gradeX" th:each="item,it : ${page.list}">
                                                <td class="text-center" th:text="${it.count}">11td>
                                                <td th:text="${item.roleName}">22td>
                                                <td th:text="${item.roleCode}">33td>
                                                <td th:text="${item.description}">33td>
                                                <td th:text="${#dates.format(item.createTime,'yyyy-MM-dd HH:mm:ss')}" >33td>
                                                <td class="text-center">
                                                    <a class="edit" th:attr="data-id=${item.id}"
                                                       sec:authorize="hasAuthority('role.edit')"
                                                       onclick="editRole()" th:href="@{/role/findById/}+${item.id}">修改a>
                                                    <a class="delete"
                                                       sec:authorize="hasAuthority('role.delete')"
                                                       th:attr="data-id=${item.id}" onclick="deleteRole()" th:href="@{/role/delete/}+${item.id}">删除a>
                                                    <a class="assgin"
                                                       sec:authorize="hasAuthority('role.assgin')"
                                                       th:attr="data-id=${item.id}">分配权限a>
                                                td>
                                            tr>
                                        tbody>
                                    table>
                                    <div class="row" th:include="common/pagination :: pagination">div>
                                div>
                            div>
                        div>
                    div>
                div>
            form>
            <script th:inline="javascript">
                function addRole(){
                    opt.openWin("/role/create","新增",630,430)
                }
    
                function editRole(){
                    //阻止默认
                    event.preventDefault()
    
                    opt.openWin(event.target.href,'修改',580,430);
                }
    
                function deleteRole(){
                    //阻止默认
                    event.preventDefault()
    
                    opt.confirm(event.target.href)
                }
    
                $(".assgin").on("click",function () {
                    var id = $(this).attr("data-id");
                    opt.openWin("/role/assignShow/"+id,'修改',580,430);
                });
            script>
        body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91

    ④ 测试

    如果用户没有相应的权限,那么按钮会消失

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Sy23ASoZ-1661871891707)(images/10/img_005.png)]

  • 相关阅读:
    【CPU设计实战】简单流水线CPU设计
    Matlab 如何选择采样频率和信号长度
    一文看懂TikTok养号
    1024程序员节特辑 | OKR VS KPI谁更合适?
    查看docker 容器的端口
    未来城市:数字孪生技术助力智慧城市构建
    ThingsBoard开源物联网平台介绍
    docker基础命令 docker镜像和docker容器的操作基础命令的思维导图
    实验送样、数据分析样品、组名命名规范
    为什么说做亚马逊站外引流必须有独立站着陆页
  • 原文地址:https://blog.csdn.net/lbw18/article/details/126614597