首先先说明一下,由于某些问题,不能将代码上传到github,但是可以展示出足够说明流程的代码和sql。
项目涉及到的技术:redis,shiro,springboot,jpa
权限分配架构:RABC模型
数据库截图
务必注意这个id=11123的exm,是用它举的例子,总结一下上面的截图:
这个id=11123的人,拥有名字叫boss的角色名,而这个叫boss的角色,拥有名为boss的权限名。
这里只展示一个功能为添加User接口的权限
一、model层
User
@Entity
@Table(name = "user")
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
@Id
private String id;
private String name;
private String city;
private String age;
private String sex;
private String dept;
private String company;
private String level;
@JsonIgnore
@ManyToMany
@JoinTable(name = "user_role",
joinColumns = {@JoinColumn(name = "user_id",referencedColumnName = "id")},
inverseJoinColumns = {@JoinColumn(name = "role_id",referencedColumnName = "id")})
private Set<Role> roles = new HashSet<>();
}
Role
@Entity
@Table(name = "role")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Role {
@Id
private String id;
private String name;
private String description;
@JsonIgnore
@ManyToMany(mappedBy = "roles")
private Set<User> users = new HashSet<>();
@JsonIgnore
@ManyToMany
@JoinTable(name = "role_permission",
joinColumns = {@JoinColumn(name = "role_id",referencedColumnName = "id")},
inverseJoinColumns = {@JoinColumn(name = "permission_id",referencedColumnName = "id")})
private Set<Permission> permissions = new HashSet<>();
}
Permission
@Entity
@Table(name = "permission")
@AllArgsConstructor
@NoArgsConstructor
@Data
@DynamicInsert(true)
@DynamicUpdate(true)
public class Permission {
@Id
private String id;
/**
* 权限名
*/
private String name;
private String type;
private String code;
private String description;
public Permission(String name, String type, String code, String description) {
this.name = name;
this.type = type;
this.code = code;
this.description = description;
}
}
至于@ManytoMany这种注解我就不多解释了,可以自己去百度。
二、dao
UserDao
public interface UserDao extends JpaSpecificationExecutor<User>, JpaRepository<User,String> {
}
RoleDao和PermissionDao同理,就不多解释了,就是继承jpa那两个而已。
三、Service
注意:这里我没有写interface,直接用的实体类。
/**
* 添加员工
*/
public void add(Map map) {
User user = new User();
user.setAge(String.valueOf(map.get("age")));
user.setCity(String.valueOf(map.get("city")));
user.setCompany("TGU");
user.setId(String.valueOf(idWorker.nextId()));
user.setLevel("3");
user.setDept(String.valueOf(map.get("dept")));
user.setSex(String.valueOf(map.get("sex")));
user.setName(String.valueOf(map.get("name")));
userDao.save(user);
}
四、Controller
@PutMapping("/add")
public Result add(@RequestBody Map map) {
userService.add(map);
return new Result(ResultCode.SUCCESS);
}
五、Realm
构建安全数据源之前,我们需要在用户认证方面,对于实际要分配的权限,进行return,换句话说,我在这里的思路是:
你登录的时候就给你一个sessionId,在这个sessionId里面携带者权限,等级相关信息,一起进行登录,这样前端拿到数据了,也可以从前端角度去控制(当然这样是很不安全的,还是从后端去控制吧)
首先构建出来你真正希望登录的时候携带的数据
ProfileResult
@Getter
@Setter
public class ProfileResult implements AuthCachePrincipal, Serializable {
private String name;
private String city;
private String level;
private Map<String, Object> roles = new HashMap<>();
public ProfileResult(User user, List<Permission> list) {
this.city = user.getCity();
this.level = user.getLevel();
this.name = user.getName();
Set<String> permissions = new HashSet<>();
for (Permission perm : list) {
String name = perm.getName();
permissions.add(name);
}
this.roles.put("permissions", permissions);
}
public ProfileResult(User user) {
this.city = user.getCity();
this.level = user.getLevel();
this.name = user.getName();
Set<Role> roles = user.getRoles();
Set<String> permissions = new HashSet<>();
for (Role role : roles) {
Set<Permission> perms = role.getPermissions();
for (Permission permission : perms) {
String name = permission.getName();
permissions.add(name);
}
}
this.roles.put("permissions", permissions);
}
@Override
public String getAuthCacheKey() {
return null;
}
}
在Realm里,我们需要完成的就是认证和授权。
在认证里,我们需要做的是验证账号密码,然后再账号密码正确的情况下,把这个人的ProfileResult给他。
在授权里,我们要将这个人的角色和权限注入到shiro内部,让后面的注解可以直接根据数据库的内容去生效。
UserRealm
public class UserRealm extends AuthorizingRealm {
@Autowired
private PasswordService passwordService;
@Autowired
private UserService userService;
@Autowired
private PermissionService permissionService;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取到安全数据
ProfileResult result = (ProfileResult) principalCollection.getPrimaryPrincipal();
//获取角色信息
Set<String> values = result.getRoles().keySet();
//获取权限信息
Set<String> strings = (Set<String>) result.getRoles().get("permissions");
//构造权限数据
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addRoles(values);
info.addStringPermissions(strings);
// SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// User user = (User) principalCollection.getPrimaryPrincipal();
// for (Role role : user.getRoles()) {
// info.addRoles(Collections.singleton(role.getName()));
// for (Permission permission : role.getPermissions()) {
// info.addStringPermissions(Collections.singleton(permission.getName()));
// }
// }
return info;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//获取到用户的名字和密码
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
String username = usernamePasswordToken.getUsername();
String password = new String(usernamePasswordToken.getPassword());
System.out.println(password);
//根据名字查询用户
User user = userService.findByName(username);
//根据用户对象获取到密码
String byId = passwordService.findById(user.getId());
//判断用户是否存在
if (user != null && password.equals(byId)) {
//构造安全数据并且返回
ProfileResult result = null;
Map map = new HashMap();
List<Permission> permissionsByName = permissionService.findAll(map);
result = new ProfileResult(user,permissionsByName);
return new SimpleAuthenticationInfo(result, byId, this.getName());//这里的东西将会被存储到SessionId里
}
//用户名和密码不匹配
return null;
}
}
六、ShiroConfiguration
上面对于数据源之类的还有认证授权都完事了,然后就将这些东西注入到Shiro的配置里面,并且标记上@Configuration注入到spring容器就生效了。
因为项目在login的时候,使用到了redis,所以也一并将redis放里面了,后面也会写出针对于redis的记录。
ShiroConfiguration
@Configuration
public class ShiroConfiguration {
//1.创建Realm
@Bean
public UserRealm getRealm() {
return new UserRealm();
}
//2. 创建安全管理器
@Bean
public SecurityManager getSecurityManager(UserRealm realm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
//将自定义的会话管理器注册到安全管理器中
securityManager.setSessionManager(sessionManager());
//将自定义的redis缓存管理器注册到安全管理器中
securityManager.setCacheManager(cacheManager());
return securityManager;
}
//开启对shior注解的支持
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
//3.配置shiro的过滤器工厂
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
//1.创建过滤器工厂
ShiroFilterFactoryBean filterFactory = new ShiroFilterFactoryBean();
//2.设置安全管理器
filterFactory.setSecurityManager(securityManager);
//3.通用配置(跳转登录页面,未授权跳转的页面)
filterFactory.setLoginUrl("/autherror?code=1");//跳转url地址
filterFactory.setUnauthorizedUrl("/autherror?code=2");//未授权的url
//4.设置过滤器集合
Map<String,String> filterMap = new LinkedHashMap<>();
//anon -- 匿名访问
filterMap.put("/user/login","anon");
filterMap.put("/autherror","anon");
filterMap.put("/user/register", "anon");
//注册
//authc -- 认证之后访问(登录)
filterMap.put("/**","authc");
//perms -- 具有某中权限 (使用注解配置授权)
filterFactory.setFilterChainDefinitionMap(filterMap);
return filterFactory;
}
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
/**
* 1.redis的控制器,操作redis
*/
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host);
redisManager.setPort(port);
return redisManager;
}
/**
* 2.sessionDao
*/
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO sessionDAO = new RedisSessionDAO();
sessionDAO.setRedisManager(redisManager());
return sessionDAO;
}
/**
* 3.会话管理器
*/
public DefaultWebSessionManager sessionManager() {
CustomSessionManager sessionManager = new CustomSessionManager();
sessionManager.setSessionIdCookieEnabled(false);
sessionManager.setSessionIdUrlRewritingEnabled(false);
sessionManager.setSessionDAO(redisSessionDAO());
return sessionManager;
}
/**
* 4.缓存管理器
*/
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
}
七、login
既然提到了redis,就顺便简单说一下login的过程,这个与此文章无关,如果单纯针对shiro,可以跳过了。
我们使用Subject,提取出安全数据源的东西,并且将sessionId,从redis中提取出来,返回给前端,在登录的时候,利用sessionId进行权限认证和授权。
/**
* 登录
*/
@PostMapping("/login")
public Result login(@RequestBody Map<String,Object> map) {
String name = String.valueOf(map.get("name"));
String password = String.valueOf(map.get("password"));
try {
//1.构造登录令牌
//加密密码
// password = new Md5Hash(password,name,3).toString();
UsernamePasswordToken token = new UsernamePasswordToken(name, password);
//获取subject
Subject subject = SecurityUtils.getSubject();
//调用login方法完成认证
subject.login(token);
//获取SessionId
String sessionId = (String) subject.getSession().getId();
return new Result(ResultCode.SUCCESS,sessionId);
} catch (Exception e) {
e.printStackTrace();
return new Result(ResultCode.MOBILEORPASSWORDERROR);
}
}
八、权限分配
最开始我们就说了,id为11123的这个人有名字叫做boss的权限,所以我们就给他标记为boss
/**
* 添加员工
*/
// @RequiresPermissions("boss")
@PutMapping("/add")
public Result add(@RequestBody Map map) {
userService.add(map);
return new Result(ResultCode.SUCCESS);
}
这里我们先打上注释,然后启动程序,去试试看
利用session进行正常登录,认证和授权如下
然后操作成功。
然后我们把注释去掉,改成一个压根就不存在的Permission去认证:
/**
* 添加员工
*/
@RequiresPermissions("asdasdasdasdasdasd")
@PutMapping("/add")
public Result add(@RequestBody Map map) {
userService.add(map);
return new Result(ResultCode.SUCCESS);
}
重新启动程序
重新认证
访问接口
明显可以看出,我们的权限生效了。
转载:https://blog.csdn.net/qq_41936805/article/details/101827346