Loading

在项目中应用Spring Security

在项目中应用Spring Security

前面我们已经学习了Spring Security框架的使用方法,本章节我们就需要将Spring Security框架应用到后台系统中进行权限控制,其本质就是认证和授权。

要进行认证和授权需要前面课程中提到的权限模型涉及的7张表支撑,因为用户信息、权限信息、菜单信息、角色信息、关联信息等都保存在这7张表中,也就是这些表中的数据是我们进行认证和授权的依据。所以在真正进行认证和授权之前需要对这些数据进行管理,即我们需要开发如下一些功能:

1、权限数据管理(增删改查)

2、菜单数据管理(增删改查)

3、角色数据管理(增删改查、角色关联权限、角色关联菜单)

4、用户数据管理(增删改查、用户关联角色)

鉴于时间关系,我们不再实现这些数据管理的代码开发。我们可以直接将数据导入到数据库中即可。

导入Spring Security环境

第一步:在health_parent父工程的pom.xml中导入Spring Security的maven坐标

<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-web</artifactId>
  <version>${spring.security.version}</version>
</dependency>
<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-config</artifactId>
  <version>${spring.security.version}</version>
</dependency>

第二步:在health_backend工程的web.xml文件中配置用于整合Spring Security框架的过滤器DelegatingFilterProxy

<!--委派过滤器,用于整合其他框架-->
<filter>
  <!--整合spring security时,此过滤器的名称固定springSecurityFilterChain-->
  <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>

实现认证和授权

第一步:在health_backend工程中按照Spring Security框架要求提供SpringSecurityUserService,并且实现UserDetailsService接口

package com.itheima.security;
​
import com.alibaba.dubbo.config.annotation.Reference;
import com.itheima.pojo.CheckItem;
import com.itheima.pojo.Permission;
import com.itheima.pojo.Role;
import com.itheima.service.CheckItemService;
import com.itheima.service.UserService;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
​
@Component
public class SpringSecurityUserService implements UserDetailsService{
    @Reference //注意:此处要通过dubbo远程调用用户服务
    private UserService userService;
  
    //根据用户名查询用户信息
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //远程调用用户服务,根据用户名查询用户信息
        com.itheima.pojo.User user = userService.findByUsername(username);
        if(user == null){
              //用户名不存在
              return null;
        }
        List<GrantedAuthority> list = new ArrayList<>();
        Set<Role> roles = user.getRoles();
        for(Role role : roles){
            //授予角色
            list.add(new SimpleGrantedAuthority(role.getKeyword()));
            Set<Permission> permissions = role.getPermissions();
            for(Permission permission : permissions){
                //授权
                list.add(new SimpleGrantedAuthority(permission.getKeyword()));
            }
        }
        
        UserDetails userDetails = new User(username,user.getPassword(),list);
        return userDetails;
    }
}
View Code

第二步:创建UserService服务接口、服务实现类、Dao接口、Mapper映射文件等

package com.itheima.service;
​
import com.itheima.pojo.User;
/**
 * 用户服务接口
 */
public interface UserService {
    public User findByUsername(String username);
}
package com.itheima.service;
​
import com.alibaba.dubbo.config.annotation.Service;
import com.itheima.dao.PermissionDao;
import com.itheima.dao.RoleDao;
import com.itheima.dao.UserDao;
import com.itheima.pojo.Permission;
import com.itheima.pojo.Role;
import com.itheima.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import java.util.Set;
​
@Service(interfaceClass = UserService.class)
@Transactional
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;
    @Autowired
    private RoleDao roleDao;
    @Autowired
    private PermissionDao permissionDao;
​
    public User findByUsername(String username) {
        User user = userDao.findByUsername(username);
        if(user == null){
            return null;
        }
        Integer userId = user.getId();
        Set<Role> roles = roleDao.findByUserId(userId);
        if(roles != null && roles.size() > 0){
            for(Role role : roles){
                Integer roleId = role.getId();
                Set<Permission> permissions = permissionDao.findByRoleId(roleId);
                if(permissions != null && permissions.size() > 0){
                    role.setPermissions(permissions);
                }
            }
            user.setRoles(roles);
        }
        return user;
    }
}
View Code
package com.itheima.dao;
​
import com.itheima.pojo.User;
​
public interface UserDao {
    public User findByUsername(String username);
}
package com.itheima.dao;
​
import com.itheima.pojo.Role;
import java.util.Set;
​
public interface RoleDao {
    public Set<Role> findByUserId(int id);
}
package com.itheima.dao;
​
import com.itheima.pojo.Permission;
import java.util.Set;
​
public interface PermissionDao {
    public Set<Permission> findByRoleId(int roleId);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.itheima.dao.UserDao" >
    <select id="findByUsername" 
            parameterType="string" 
            resultType="com.itheima.pojo.User">
        select * from t_user where username = #{username}
    </select>
</mapper>
View Code
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.itheima.dao.RoleDao" >
    <select id="findByUserId" 
            parameterType="int" 
            resultType="com.itheima.pojo.Role">
        select  r.* 
            from t_role r ,t_user_role ur 
            where r.id = ur.role_id and ur.user_id = #{userId}
    </select>
</mapper>
View Code
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.itheima.dao.PermissionDao" >
    <select id="findByRoleId" 
            parameterType="int" 
            resultType="com.itheima.pojo.Permission">
        select  p.* 
            from t_permission p ,t_role_permission rp 
            where p.id = rp.permission_id and rp.role_id = #{roleId}
    </select>
</mapper>
View Code

第三步:修改health_backend工程中的springmvc.xml文件,修改dubbo批量扫描的包路径

<!--批量扫描-->
<dubbo:annotation package="com.itheima" />

注意:此处原来扫描的包为com.itheima.controller,现在改为com.itheima包的目的是需要将我们上面定义的SpringSecurityUserService也扫描到,因为在SpringSecurityUserService的loadUserByUsername方法中需要通过dubbo远程调用名称为UserService的服务。

第四步:在health_backend工程中提供spring-security.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:security="http://www.springframework.org/schema/security"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/mvc
                        http://www.springframework.org/schema/mvc/spring-mvc.xsd
                        http://code.alibabatech.com/schema/dubbo
                        http://code.alibabatech.com/schema/dubbo/dubbo.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context.xsd
                          http://www.springframework.org/schema/security
                          http://www.springframework.org/schema/security/spring-security.xsd">
<!--
        http:用于定义相关权限控制
        指定哪些资源不需要进行权限校验,可以使用通配符
    -->
    <security:http security="none" pattern="/js/**" />
    <security:http security="none" pattern="/css/**" />
    <security:http security="none" pattern="/img/**" />
    <security:http security="none" pattern="/plugins/**" />
  
    <!--
        http:用于定义相关权限控制
        auto-config:是否自动配置
                        设置为true时框架会提供默认的一些配置,例如提供默认的登录页面、登出处理等
                        设置为false时需要显示提供登录表单配置,否则会报错
        use-expressions:用于指定intercept-url中的access属性是否使用表达式
    -->
    <security:http auto-config="true" use-expressions="true">
        <security:headers>
            <!--设置在页面可以通过iframe访问受保护的页面,默认为不允许访问-->
            <security:frame-options policy="SAMEORIGIN"></security:frame-options>
        </security:headers>
        <!--
            intercept-url:定义一个拦截规则
            pattern:对哪些url进行权限控制
            access:在请求对应的URL时需要什么权限,默认配置时它应该是一个以逗号分隔的角色列表,
                  请求的用户只需拥有其中的一个角色就能成功访问对应的URL
            isAuthenticated():已经经过认证(不是匿名用户)
        -->
        <security:intercept-url pattern="/pages/**"  access="isAuthenticated()" />
        <!--form-login:定义表单登录信息-->
        <security:form-login login-page="/login.html"
                             username-parameter="username"
                             password-parameter="password"
                             login-processing-url="/login.do"
                             default-target-url="/pages/main.html"
                             always-use-default-target="true"
                             authentication-failure-url="/login.html"
        />
        
        <!--
            csrf:对应CsrfFilter过滤器
            disabled:是否启用CsrfFilter过滤器,如果使用自定义登录页面需要关闭此项,
                    否则登录操作会被禁用(403-->
        <security:csrf disabled="true"></security:csrf>
    </security:http><!--配置密码加密对象-->
    <bean id="passwordEncoder" 
          class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" /><!--认证管理器,用于处理认证操作-->
    <security:authentication-manager>
        <!--认证提供者,执行具体的认证逻辑-->
        <security:authentication-provider user-service-ref="springSecurityUserService">
            <!--指定密码加密策略-->
            <security:password-encoder ref="passwordEncoder" />
        </security:authentication-provider>
    </security:authentication-manager><!--开启注解方式权限控制-->
    <security:global-method-security pre-post-annotations="enabled" />
</beans>
View Code

第五步:在springmvc.xml文件中引入spring-security.xml文件

<import resource="spring-security.xml"></import>

第六步:在Controller的方法上加入权限控制注解,此处以CheckItemController为例

package com.itheima.controller;
​
import com.alibaba.dubbo.config.annotation.Reference;
import com.itheima.constant.MessageConstant;
import com.itheima.constant.PermissionConstant;
import com.itheima.entity.PageResult;
import com.itheima.entity.QueryPageBean;
import com.itheima.entity.Result;
import com.itheima.exception.CustomException;
import com.itheima.pojo.CheckItem;
import com.itheima.pojo.Member;
import com.itheima.service.CheckItemService;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import java.util.List;
​
/**
 * 体检检查项管理
 */
@RestController
@RequestMapping("/checkitem")
public class CheckItemController {
    @Reference
    private CheckItemService checkItemService;
​
    //分页查询
    @PreAuthorize("hasAuthority('CHECKITEM_QUERY')")//权限校验
    @RequestMapping("/findPage")
    public PageResult findPage(@RequestBody QueryPageBean queryPageBean){
        PageResult pageResult = checkItemService.pageQuery(
                                    queryPageBean.getCurrentPage(), 
                                    queryPageBean.getPageSize(), 
                                    queryPageBean.getQueryString());
        return pageResult;
    }
​
    //删除
    @PreAuthorize("hasAuthority('CHECKITEM_DELETE')")//权限校验
    @RequestMapping("/delete")
    public Result delete(Integer id){
        try {
            checkItemService.delete(id);
        }catch (RuntimeException e){
            return new Result(false,e.getMessage());
        }catch (Exception e){
            return new Result(false, MessageConstant.DELETE_CHECKITEM_FAIL);
        }
        return new Result(true,MessageConstant.DELETE_CHECKITEM_SUCCESS);
    }
​
    //新增
    @PreAuthorize("hasAuthority('CHECKITEM_ADD')")//权限校验
    @RequestMapping("/add")
    public Result add(@RequestBody CheckItem checkItem){
        try {
            checkItemService.add(checkItem);
        }catch (Exception e){
            return new Result(false,MessageConstant.ADD_CHECKITEM_FAIL);
        }
        return new Result(true,MessageConstant.ADD_CHECKITEM_SUCCESS);
    }
​
    //编辑
    @PreAuthorize("hasAuthority('CHECKITEM_EDIT')")//权限校验
    @RequestMapping("/edit")
    public Result edit(@RequestBody CheckItem checkItem){
        try {
            checkItemService.edit(checkItem);
        }catch (Exception e){
            return new Result(false,MessageConstant.EDIT_CHECKITEM_FAIL);
        }
        return new Result(true,MessageConstant.EDIT_CHECKITEM_SUCCESS);
    }
}
View Code

第七步:修改页面,没有权限时提示信息设置,此处以checkitem.html中的handleDelete方法为例

//权限不足提示
showMessage(r){
  if(r == 'Error: Request failed with status code 403'){
    //权限不足
    this.$message.error('无访问权限');
    return;
  }else{
    this.$message.error('未知错误');
    return;
  }
}
// 删除
handleDelete(row) {
  this.$confirm('此操作将永久当前数据,是否继续?', '提示', {
    type: 'warning'
  }).then(()=>{
    //点击确定按钮执行此代码
    axios.get("/checkitem/delete.do?id=" + row.id).then((res)=> {
      if(!res.data.flag){
        //删除失败
        this.$message.error(res.data.message);
      }else{
        //删除成功
        this.$message({
          message: res.data.message,
          type: 'success'
        });
        this.findPage();
      }
    }).catch((r)=>{
      this.showMessage(r);
    });
  }).catch(()=> {
    //点击取消按钮执行此代码
    this.$message('操作已取消');
  });
}
View Code

显示用户名

前面我们已经完成了认证和授权操作,如果用户认证成功后需要在页面展示当前用户的用户名。Spring Security在认证成功后会将用户信息保存到框架提供的上下文对象中,所以此处我们就可以调用Spring Security框架提供的API获取当前用户的username并展示到页面上。

实现步骤:

第一步:在main.html页面中修改,定义username模型数据基于VUE的数据绑定展示用户名,发送ajax请求获取username

<script>
    new Vue({
        el: '#app',
        data:{
            username:null,//用户名
            menuList:[]
        },
        created(){
            //发送请求获取当前登录用户的用户名
            axios.get('/user/getUsername.do').then((response)=>{
                this.username = response.data.data;
            });
        }
    });
</script>
View Code
<div class="avatar-wrapper">
  <img src="../img/user2-160x160.jpg" class="user-avatar">
  <!--展示用户名-->
  {{username}}
</div>

第二步:创建UserController并提供getUsername方法

package com.itheima.controller;
​
import com.itheima.constant.MessageConstant;
import com.itheima.entity.Result;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
​
@RestController
@RequestMapping("/user")
public class UserController {
    //获取当前登录用户的用户名
    @RequestMapping("/getUsername")
    public Result getUsername()throws Exception{
        try{
            org.springframework.security.core.userdetails.User user =
                    (org.springframework.security.core.userdetails.User)
                    SecurityContextHolder.getContext().getAuthentication().getPrincipal();
            return new Result(true, MessageConstant.GET_USERNAME_SUCCESS,user.getUsername());
        }catch (Exception e){
            return new Result(false, MessageConstant.GET_USERNAME_FAIL);
        }
    }
}
View Code

通过debug调试可以看到Spring Security框架在其上下文中保存的用户相关信息:

用户退出

第一步:在main.html中提供的退出菜单上加入超链接

<el-dropdown-item divided>
  <span style="display:block;"><a href="/logout.do">退出</a></span>
</el-dropdown-item>

第二步:在spring-security.xml文件中配置

<!--
  logout:退出登录
  logout-url:退出登录操作对应的请求路径
  logout-success-url:退出登录后的跳转页面
-->
<security:logout logout-url="/logout.do" 
                 logout-success-url="/login.html" invalidate-session="true"/>

 

posted @ 2021-07-14 17:05  1640808365  阅读(349)  评论(0编辑  收藏  举报