功能需求:通过角色权限配置,实现不同角色功能的添加删除
文章从底层开始解说,依次为数据库-pojo层-mapper层-service层-shiro配置-controller层-前端页面一点点
数据库设计:
思路梳理
每个用户对应一个角色(一对一);每种角色对应多个权限(一对多),所以角色与权限之间应建立一个中间表,管理角色对应的多个权限
表设计
user表(与角色表id之间一对一关系):
role表:
permission权限表(其中permissionLogo为最终shiro注解添加权限要注入的值,留个标记-插个眼):
rolepermission角色权限表(实现角色与权限之间一对多的关系):
pojo层:
对应数据库创建User,Role,Permission,RolePermission四个实体类
User类
@Datapublic class User {
private Integer id; //用户id
private String userCode; //用户账号
private String userName; //用户名称
private String password; //密码
private Integer userRoleId; //用户角色id
}
Role类
@Data/**
* 角色表
*/
public class Role {
private Integer id; //角色id
private String roleCode; //角色编码
private String roleName; //角色名称
}
permission类
@Datapublic class Permission {
private Integer id; //权限id
private String permissionName; //权限名称
private String url; //权限url
private String permissionLogo; //权限标识
}
RolePermission类
@Datapublic class RolePermission {
private Integer id; //角色-权限表关联主键id
private Integer roleId; //角色id
private Integer permissionId; //权限id
}
mapper层
需要两个mapper:UserMapper、PermissionMapper分别实现数据库数据的增删改查
UserMapper
UserMapper主要实现根据用户名实现用户的查询(为什么没有密码呢?因为shiro层会帮我们做,下面你就知道了-再插个眼(写到下面回来补充一嘴,UserRealm类中的doGetAuthenticationInfo方法配合Controller层中的登录实现用户名密码的认证)),实现用户的登录和认证
@Mapperpublic interface UserMapper {
// 根据名称查询用户 @Select("select * from user where userName=#{userName}")
User getUserByUserNameLogin(@Param("userName") String userName);
}
PermissionMapper
PermissionMapper实现四个功能,根据角色id查出所有的权限id,再根据权限id查询权限logo,这两个功能已经能实现用户权限的赋予,后面两个功能实现角色权限的添加与删除
@Mapperpublic interface PermissionMapper {
// 根据角色id查出所有的权限id
@Select("select permissionId from rolepermission where roleId=#{roleId}")
List<Integer> getRolePermissionByRoleId(@Param("roleId") Integer roleId);
// 根据权限id查询权限logo
@Select("select permissionLogo from permission where id=#{id}")
String getPermissionLogoById(@Param("id") Integer id);
//权限的添加与修改,就是角色权限表里内容的添加与删除,因为使用checkbox进行添加删除,不用担心重复添加或删除的意外
//根据能否在角色权限表里查询到权限id对应的行,判断删除或添加权限
@Select("select count(1) from rolepermission where roleId=#{roleId} and permissionId=#{permissionId}")
int getPermissionCountByPermissionAndRoleId(@Param("roleId") Integer roleId,@Param("permissionId") Integer permissionId);
// 添加权限
@Insert("insert into rolepermission(roleId,permissionId) values(#{roleId},#{permissionId})")
int addPermission(@Param("roleId") Integer roleId,@Param("permissionId") Integer permissionId);
// 删除权限
@Delete("delete from rolepermission where roleId=#{roleId} and permissionId=#{permissionId}")
int deletePermission(@Param("roleId") Integer roleId,@Param("permissionId") Integer permissionId);
}
service层
UserMapper和PermissionMapper有其对应的service层:
UserService
public interface UserService {
// 根据名称查询用户 User getUserByUserNameLogin(String userName);
}
UserServiceImpl
@Servicepublic class UserServiceImpl implements UserService{
@Resource
private UserMapper userMapper;
/** * 根据名称查询用户
* @param userName
* @return
*/
@Override
public User getUserByUserNameLogin(String userName) {
return userMapper.getUserByUserNameLogin(userName);
}
}
偷个懒,,,servce层相信大家都会,对!一定都会,都是苗子啊~
shiro配置
shiro需要一个配置类ShiroCofig以及一个自定义类UserRealm
ShiroConfig配置类
@Configurationpublic class ShiroConfig {
// 3 ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
// 设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
/**
* anon:无需认证就可以访问
* authc:必须认证了才能访问
* user:必须拥有 记住我 功能才能用
* perms:拥有对某个资源的权限才能访问
* role:拥有某个角色权限才能访问
*/
Map<String, String> filterMap = new LinkedHashMap<>();
// 注意顺序,先认证后授权
// 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 :这是一个坑呢,一不小心代码就不好使了;
// 授权使用注解在controller中实现
// 设置登录的请求
// bean.setLoginUrl("/toLogin");
//我这里没做未授权的页面跳转的url,是因为setUnauthorizedUrl()没起作用,最后会给出解决方法
bean.setFilterChainDefinitionMap(filterMap);
return bean;
}
// 2 DefaultWebSecurityManager
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 关联UserRealm
securityManager.setRealm(userRealm);
return securityManager;
}
// 1 创建realm对象,需自定义类
@Bean
public UserRealm userRealm() {
return new UserRealm();
}
// 开启对shiro注解的支持
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(defaultWebSecurityManager);
return advisor;
}
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
autoProxyCreator.setProxyTargetClass(true);
return autoProxyCreator;
}
//整合ShiroDialect:用来整合shiro thymeleaf
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
}
UserRealm类
UserRealm类中的doGetAuthenticationInfo方法配合controller层中的登录实现用户名密码的认证
//自定义的UserRealm extends AuthorizingRealmpublic class UserRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Autowired
private PermissionService permissionService;
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了=>授权doGetAuthorizationInfo");
// SimpleAuthorizationInfo
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 拿到当前登录的这个对象
Subject subject = SecurityUtils.getSubject();
User currentUser = (User) subject.getPrincipal(); //拿到User对象
// 设置当前用户的权限
Set<String> permissions = new HashSet<>();
List<Integer> permissionIdList = permissionService.getRolePermissionByRoleId(currentUser.getUserRoleId());
for (int i = 0; i < permissionIdList.size(); i++) {
String permissionLogo = permissionService.getPermissionLogoById(permissionIdList.get(i));
System.out.println("Logo----------" + permissionLogo);
permissions.add(permissionLogo);
}
info.addStringPermissions(permissions);
return info;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了=>授权==认证doGetAuthenticationInfo");
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
// 用户名,密码~ 连接数据库获取
User user = userService.getUserByUserNameLogin(userToken.getUsername());
if (user == null) { //表示没有这个人
return null; //抛出异常 UnknownAccountException
}
// 密码认证,shiro做
return new SimpleAuthenticationInfo(user, user.getPassword(), "");
}
}
Controller层
下面的login方法与UserRealm中的认证联合实现登录与认证,最后的两个方法add和update实现权限的授权,至此其实已经实现了权限进入页面了,再往下简单介绍一些权限的添加与删除
@Controllerpublic class LoginController {
@Autowired
private UserService userService;
@RequestMapping({"/", "/toLogin"})
public String toLogin(){
return "login";
}
@RequestMapping("/dologin")
public String login(String userName, String password, Model model){
// 获取当前用户
Subject subject = SecurityUtils.getSubject();
// 封装用户的登录数据
UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
try {
subject.login(token); //执行登录方法,若无异常说明OK了
return "home";
} catch (UnknownAccountException e) { // 用户名不存在
model.addAttribute("msg","用户名错误");
return "login";
}catch (IncorrectCredentialsException e) { // 密码不存在
model.addAttribute("msg","密码错误");
return "login";
}
}
@RequiresPermissions("add")
@RequestMapping("/add")
public String add(){
return "test";
}
@RequiresPermissions("update")
@RequestMapping("/update")
public String update(){
return "test1";
}
}
前端HTML
<html lang="zz" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro"
>
....
<li shiro:hasPermission="workstation:one"> <a class="J_menuItem" th:href="@{/add}" data-index="0">个人资料修改</a>
</li>
添加-删除权限
简单提一下,因为权限的配置和数据库表已经弄好了,权限的添加和删除就是往数据库rolepermission表添加数据即可(我的permission权限表是固定的)
setUnauthorizedUrl()未授权页面不起作用解决
网上有很多解决方法,大家也可以点进去setUnauthorizedUrl()方法自己研究下,会发现....,我也没看懂,不过解决办法倒是有了,自己定义一个全局异常捕获类就行
@ControllerAdvice@Order(value = Ordered.HIGHEST_PRECEDENCE)
public class GlobalExceptionHandler {
@ExceptionHandler(value = AuthorizationException.class)
public String handleAuthorizationException() {
// 点击未授权的链接,进入的未授权提示页 return "/error/noauth";
}
}
不过其实前端只要加上shiro:hasPermission="workstation:one"该语句后,未授权的页面链接根本不会显示,大家看自己的需求