众所周知,作为一个后端新手学习者,通过项目来学习,增长项目经验,是一个非常好的学习途径,所以我就找到了一个个人博客的项目,经过自己的调试和学习,现在已经比较完全的掌握了这个项目的创作过程,和一些细节方面的东西,在这里我把项目源码和项目实例都给出来,并且在后面进行一些细节和整体思路上的详解。
众所周知,shiro和springsecurity作为两个著名的框架,被人们广泛使用,当然这两个框架各有利弊,shiro是一个相对来说简单一点,好理解一点,所以大部分的项目都会使用这个框架进行安全认证,个人认为也是比较易上手的一个简单框架,但是SpringSecurity作为一个Spring家族的一员,是一个非常成熟而且功能强大的安全框架,在这里我们就来详细的介绍一下shiro的内容,源码也会在我的gitee仓库中,是一个比较好看的个人博客,欢迎大家来进行点评。
package com.danli;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.FileType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
// 演示例子,执行 main 方法控制台输入模块表名回车自动生成对应项目目录中
public class CodeGenerator {
/**
*
* 读取控制台内容
*
*/
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotEmpty(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/vueblog/src/main/java");
// gc.setOutputDir("D:\\test");
gc.setAuthor("fanfanli");
gc.setOpen(false);
// gc.setSwagger2(true); 实体属性 Swagger2 注解
gc.setServiceName("%sService");
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/vueblog?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=UTC");
// dsc.setSchemaName("public");b
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("1234");
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName(null);
pc.setParent("com.danli");
mpg.setPackageInfo(pc);
// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};
cfg.setFileCreate((configBuilder, fileType, filePath) -> {
//如果是Entity则直接返回true表示写文件
if (fileType == FileType.ENTITY) {
return true;
}
//否则先判断文件是否存在
File file = new File(filePath);
boolean exist = file.exists();
if (!exist) {
file.getParentFile().mkdirs();
}
//文件不存在或者全局配置的fileOverride为true才写文件
return !exist || configBuilder.getGlobalConfig().isFileOverride();
});
// 如果模板引擎是 freemarker
String templatePath = "/templates/mapper.xml.ftl";
// 如果模板引擎是 velocity
// String templatePath = "/templates/mapper.xml.vm";
// 自定义输出配置
List<FileOutConfig> focList = new ArrayList<>();
// 自定义配置会被优先输出
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
return projectPath + "/vueblog/src/main/resources/mapper/"
+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
// 配置模板
TemplateConfig templateConfig = new TemplateConfig();
templateConfig.setXml(null);
mpg.setTemplate(templateConfig);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix("m_");
mpg.setStrategy(strategy);
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.execute();
}
}
sql文件也在我的项目中,在此不做过多叙述。
package com.danli.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.danli.common.lang.vo.UserInfo;
import com.danli.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
*
* Mapper 接口
*
*
* @author fanfanli
* @date 2021-04-08
*/
@Mapper
@Repository
public interface UserMapper extends BaseMapper<User> {
/**
* 获取用户部分信息list
*/
@Select("select id, nickname, username, avatar, email, status, create_time, update_time, role from user order by create_time desc")
List<UserInfo> getUserInfo();
}
package com.danli.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.danli.common.lang.vo.UserInfo;
import com.danli.entity.User;
import java.util.List;
/**
* user服务类
*
* @author fanfanli
* @date 2021/4/5
*/
public interface UserService extends IService<User> {
/**
* 查询所有用户(只含有部分信息)
*
* @return 用户(只含有部分信息)list
*/
List<UserInfo> getUserInfoList();
}
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.danli.common.lang.vo.UserInfo;
import com.danli.entity.User;
import com.danli.mapper.UserMapper;
import com.danli.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* user服务实现类
*
* @author fanfanli
* @date 2021-04-08
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Autowired
UserMapper userMapper;
/**
* 查询所有用户(只含有部分信息)
*
* @return 用户(只含有部分信息)list
*/
@Override
public List<UserInfo> getUserInfoList(){
List<UserInfo> userInfos = userMapper.getUserInfo();
return userInfos;
}
}
至此,关于数据库链接的步骤就完成了,下面我们开始进行加密和授权以及最重要的认证
概念:数据加密的基本过程就是对原来为明文的文件或数据按照某种算法进行处理,使其成为不可读的一段代码,通常称为“密文”,使其只能在输入相应的密匙之后才能显示出本来内容,通过这样的途径来达到保护数据不被非法人窃取、阅读的目的。该过程的逆过程为解密,即将该编码信息转换为其原来数据的过程。
加密的分类:
(1)、对称加密
双方使用的同一个密匙,既可以加密又可以解密,这种加密方法称为对称加密,也称单密匙加密。
(2)、非对称加密
一对密匙由公钥和私钥组成(可以使用很多对密匙)。私钥解密公钥加密数据,公钥解密私钥加密数据(私钥公钥可以互相加密解密)。
加密算法分类
(1)、单项加密
单项加密是不可逆的,也就是只能加密,不能解密。通常用来传输类型用户名和密码,直接将加密后的数据提交到后台,因为后台不需要知道用户名和密码,可以直接将接收到的加密后的数据存储到数据库
(2)、双向加密
通常分为对称性加密算法和非对称性加密算法,对于对称性加密算法,信息接收双方都需事先知道密匙和加解密算法且其密匙是相同的,之后便是对数据进行 加解密了。非对称算法与之不同,发送双方A,B事先均生成一堆密匙,然后A将自己的公有密匙发送给B,B将自己的公有密匙发送给A,如果A要给B发送消 息,则先需要用B的公有密匙进行消息加密,然后发送给B端,此时B端再用自己的私有密匙进行消息解密,B向A发送消息时为同样的道理。
import org.apache.shiro.crypto.hash.Md5Hash;
public class Md5HashTest {
public static void main(String[] args) {
// 对单个信息加密
Md5Hash md5 = new Md5Hash("123456");
System.out.println(md5.toString());
// 加密添加盐值 增大解密难度,让密码更咸一点
md5 = new Md5Hash("123456","aaa");
System.out.println(md5.toString());
// 加密添加盐值 增大解密难度 迭代1024次
md5 = new Md5Hash("123456789","aaa",1024);
System.out.println(md5);
}
}
package com.danli.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* jwt工具类
*/
@Slf4j
@Data
@Component
@ConfigurationProperties(prefix = "fanli.jwt")
public class JwtUtils {
private String secret;
private long expire;
private String header;
/**
* 生成 JWT Token 字符串
*
* @param userId 签发人id
* expireDate 过期时间 签发时间
* claims 额外添加到荷部分的信息。
* 例如可以添加用户名、用户ID、用户(加密前的)密码等信息
*/
public String generateToken(long userId,String username) {
Date nowDate = new Date();
//过期时间
Date expireDate = new Date(nowDate.getTime() + expire * 1000);
Map<String, Object> claims = new HashMap<String, Object>();//创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的)
claims.put("userId", userId+"");
claims.put("username", username);
return Jwts.builder() // 创建 JWT 对象
.setHeaderParam("typ", "JWT") //设置头部信息
.setClaims(claims) // 设置私有声明
.setIssuedAt(nowDate) //设置payload的签发时间
.setExpiration(expireDate) //设置payload的过期时间
.signWith(SignatureAlgorithm.HS512, secret) // 设置安全密钥(生成签名所需的密钥和算法)
.compact(); //生成token(1.编码 Header 和 Payload 2.生成签名 3.拼接字符串)
}
/**
* 解析token
* JWT Token 由 头部 荷载部 和 签名部 三部分组成。签名部分是由加密算法生成,无法反向解密。
* 而 头部 和 荷载部分是由 Base64 编码算法生成,是可以反向反编码回原样的。
* 这也是为什么不要在 JWT Token 中放敏感数据的原因。
*
* @param token 加密后的token
* @return claims 返回荷载部分的键值对
*/
public Claims getClaimByToken(String token) {
try {
return Jwts.parser() // 创建解析对象
.setSigningKey(secret) // 设置安全密钥(生成签名所需的密钥和算法)
.parseClaimsJws(token) // 解析token
.getBody(); // 获取 payload 部分内容
} catch (Exception e) {
log.debug("validate is token error ", e);
return null;
}
}
/**
* token是否过期
*
* @return true:过期
*/
public boolean isTokenExpired(Date expiration) {
return expiration.before(new Date());
}
}
package com.danli.shiro;
import org.apache.shiro.authc.AuthenticationToken;
/**
* Jwt
*
* @author fanfanli
* @date 2021/5/28
*/
public class JwtToken implements AuthenticationToken {
private String token;
public JwtToken(String token) {
this.token = token;
}
@Override
public Object getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token;
}
}
package com.danli.shiro;
import cn.hutool.json.JSONUtil;
import com.danli.common.lang.Result;
import com.danli.util.JwtUtils;
import io.jsonwebtoken.Claims;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.ExpiredCredentialsException;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* shiro过滤器
*
* @author fanfanli
* @date 2021/5/28
*/
@Component
public class JwtFilter extends AuthenticatingFilter {
@Autowired
JwtUtils jwtUtils;
@Override
protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
// 获取 token
HttpServletRequest request = (HttpServletRequest) servletRequest;
String jwt = request.getHeader("Authorization");
if (!StringUtils.hasLength(jwt)) {
return null;
}
return new JwtToken(jwt);
}
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String token = request.getHeader("Authorization");
if (!StringUtils.hasLength(token)) {
return true;
} else {
// 判断是否已过期
Claims claim = jwtUtils.getClaimByToken(token);
if (claim == null || jwtUtils.isTokenExpired(claim.getExpiration())) {
throw new ExpiredCredentialsException("token已失效,请重新登录!");
}
}
// 执行自动登录
return executeLogin(servletRequest, servletResponse);
}
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
try {
//处理登录失败的异常
Throwable throwable = e.getCause() == null ? e : e.getCause();
Result r = Result.fail(throwable.getMessage());
String json = JSONUtil.toJsonStr(r);
httpResponse.getWriter().print(json);
} catch (IOException e1) {
}
return false;
}
/**
* 对跨域提供支持
*/
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
// 跨域时会首先发送一个OPTIONS请求,这里我们给OPTIONS请求直接返回正常状态
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
httpServletResponse.setStatus(org.springframework.http.HttpStatus.OK.value());
return false;
}
return super.preHandle(request, response);
}
}
package com.danli.config;
import com.danli.shiro.AccountRealm;
import com.danli.shiro.JwtFilter;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* shiro配置类
*
* @author fanfanli
* @date 2021/4/8
*/
@Configuration
public class ShiroConfig {
@Autowired
JwtFilter jwtFilter;
@Bean
public SessionManager sessionManager(RedisSessionDAO redisSessionDAO) {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
// inject redisSessionDAO
sessionManager.setSessionDAO(redisSessionDAO);
return sessionManager;
}
@Bean
public DefaultWebSecurityManager securityManager(AccountRealm accountRealm,
SessionManager sessionManager,
RedisCacheManager redisCacheManager) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(accountRealm);
//inject sessionManager
securityManager.setSessionManager(sessionManager);
//关闭shiro自带的session
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
securityManager.setSubjectDAO(subjectDAO);
// inject redisCacheManager
securityManager.setCacheManager(redisCacheManager);
return securityManager;
}
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition() {
DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/**", "jwt");
chainDefinition.addPathDefinitions(filterMap);
return chainDefinition;
}
@Bean("shiroFilterFactoryBean")
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,
ShiroFilterChainDefinition shiroFilterChainDefinition) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
Map<String, Filter> filters = new HashMap<>();
//使用jwtFilter过滤器
filters.put("jwt", jwtFilter);
shiroFilter.setFilters(filters);
Map<String, String> filterMap = shiroFilterChainDefinition.getFilterChainMap();
shiroFilter.setFilterChainDefinitionMap(filterMap);
return shiroFilter;
}
/**
* 解决aop与shiro冲突问题
*/
@Bean
public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setUsePrefix(true);
return defaultAdvisorAutoProxyCreator;
}
}
package com.danli.shiro;
import lombok.Data;
import java.io.Serializable;
/**
* 账户信息实体类,用来介绍账户信息
*
* @author fanfanli
* @date 2021/5/28
*/
@Data
public class AccountProfile implements Serializable {
private Long id;
private String username;
private String avatar;
private String role;//角色
}
package com.danli.shiro;
import cn.hutool.core.bean.BeanUtil;
import com.danli.entity.User;
import com.danli.service.UserService;
import com.danli.util.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 登录认证和授权
*
* @author fanfanli
* @date 2021/5/28
*/
@Slf4j
@Component
public class AccountRealm extends AuthorizingRealm {
@Autowired
JwtUtils jwtUtils;
@Autowired
UserService userService;
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JwtToken;
}
/**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
log.info("执行doGetAuthorizationInfo方法进行授权");
// String username = JwtUtil.getUsername(principalCollection.toString());
log.info("登录的用户:"+principals.toString());
// log.info("登录的用户:" + username);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
AccountProfile accountProfile = (AccountProfile)principals.getPrimaryPrincipal();
String[] roles = accountProfile.getRole().split(",");
log.info("roles");
for(String role : roles){
info.addRole(role);
if(role.equals("role_root")){
info.addStringPermission("user:create");
info.addStringPermission("user:update");
info.addStringPermission("user:read");
info.addStringPermission("user:delete");
}
else if( role.equals("role_admin")){
info.addStringPermission("user:read");
info.addStringPermission("user:create");
info.addStringPermission("user:update");
}
else if( role.equals("role_user")){
info.addStringPermission("user:read");
info.addStringPermission("user:create");
}
else if(role.equals("role_guest")){
info.addStringPermission("user:read");
}
}
return info;
}
/**
* 认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
JwtToken jwt = (JwtToken) token;
log.info("jwt----------------->{}", jwt);
String userId = (String) jwtUtils.getClaimByToken((String) jwt.getPrincipal()).get("userId");
String username = (String) jwtUtils.getClaimByToken((String) jwt.getPrincipal()).get("username");
User user = userService.getById(Long.parseLong(userId));
if (user == null) {
throw new UnknownAccountException("账户不存在!");
}
if (user.getStatus() == -1) {
throw new LockedAccountException("账户已被锁定!");
}
if(!user.getUsername().equals(username)){
throw new UnknownAccountException("userId与username不一致");
}
AccountProfile profile = new AccountProfile();
//知道它的身份 principals
BeanUtil.copyProperties(user, profile);
log.info("profile----------------->{}", profile.toString());
return new SimpleAuthenticationInfo(profile, jwt.getCredentials(), getName());
}
}
可能代码量有一些大,下面我们来进行详情的介绍。什么是授权
授权可简单理解为who对what(which)进行How操作:
Who,即主体(Subject),主体需要访问系统中的资源。
What,即资源(Resource),如系统菜单、页面、按钮、类方法、系统商品信息等。资源包括资源类型和资源实例,比如商品信息为资源类型,类型为t01的商品为资源实例,编号为001的商品信息也属于资源实例。
How,权限/许可(Permission),规定了主体对资源的操作许可,权限离开资源没有意义,如用户查询权限、用户添加权限、某个类方法的调用权限、编号为001用户的修改权限等,通过权限可知主体对哪些资源都有哪些操作许可。
授权方式
基于角色的访问控制,RBAC基于角色的访问控制(Role-Based Access Control)是以角色为中心进行访问控制
if(subject.hasRole("admin")){
//操作什么资源
}
基于资源的访问控制
RBAC基于资源的访问控制(Resource-Based Access Control)是以资源为中心进行访问控制
if(subject.isPermission("user:update:01")){ //资源实例
//对01用户进行修改
}
if(subject.isPermission("user:update:*")){ //资源类型
//对01用户进行修改
}
权限字符串
权限字符串的规则是:资源标识符:操作:资源实例标识符。
意思是对哪个资源的哪个实例具有什么操作,“:”是资源/操作/实例的分割符,权限字符串也可以使用*通配符。
例子:
用户创建权限:user:create,或user:create:*
用户修改实例001的权限:user:update:001
用户实例001的所有权限:user:*:001
权限的编码方式
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole("admin")) {
//有权限
} else {
//无权限
}
@RequiresRoles("admin")
public void hello() {
//有权限
}
分析代码实例:这里通过principals参数的传递,进行数据的传输,然后从参数中提取出用户的角色,通过这个角色来判断权限,并且赋予他相对应的权限,权限赋予之后,我们就返回这个数组,这个数组就成为了这个用户的功能权限数组,在后面验证就能直接进行判断。因为这个个人博客只有简单的修改,删除,添加,查看几个操作,在这里也就不再去多设置别的数据库来专门存储这个权限,当然如果是功能非常多,就最好单独设置这个数据库,然后通过多表查询,来进行权限授权,这个方法过段时间我会通过SpringSecurity来详细的讲一下,敬请期待。
/**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
log.info("执行doGetAuthorizationInfo方法进行授权");
log.info("登录的用户:"+principals.toString());
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
AccountProfile accountProfile = (AccountProfile)principals.getPrimaryPrincipal();
String[] roles = accountProfile.getRole().split(",");
log.info("roles");
for(String role : roles){
info.addRole(role);
if(role.equals("role_root")){
info.addStringPermission("user:create");
info.addStringPermission("user:update");
info.addStringPermission("user:read");
info.addStringPermission("user:delete");
}
else if( role.equals("role_admin")){
info.addStringPermission("user:read");
info.addStringPermission("user:create");
info.addStringPermission("user:update");
}
else if( role.equals("role_user")){
info.addStringPermission("user:read");
info.addStringPermission("user:create");
}
else if(role.equals("role_guest")){
info.addStringPermission("user:read");
}
}
return info;
}
/**
* 认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
JwtToken jwt = (JwtToken) token;
log.info("jwt----------------->{}", jwt);
String userId = (String) jwtUtils.getClaimByToken((String) jwt.getPrincipal()).get("userId");
String username = (String) jwtUtils.getClaimByToken((String) jwt.getPrincipal()).get("username");
User user = userService.getById(Long.parseLong(userId));
if (user == null) {
throw new UnknownAccountException("账户不存在!");
}
if (user.getStatus() == -1) {
throw new LockedAccountException("账户已被锁定!");
}
if(!user.getUsername().equals(username)){
throw new UnknownAccountException("userId与username不一致");
}
AccountProfile profile = new AccountProfile();
//知道它的身份 principals
BeanUtil.copyProperties(user, profile);
log.info("profile----------------->{}", profile.toString());
return new SimpleAuthenticationInfo(profile, jwt.getCredentials(), getName());
}
package com.danli.controller;
import cn.hutool.core.map.MapUtil;
import cn.hutool.crypto.SecureUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.danli.common.lang.Result;
import com.danli.common.lang.dto.LoginDto;
import com.danli.entity.User;
import com.danli.service.UserService;
import com.danli.util.JwtUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
/**
* 登录登出控制器
*
* @author fanfanli
* @date 2021/4/8
*/
@RestController
public class AccountController {
@Autowired
JwtUtils jwtUtils;
@Autowired
UserService userService;
/**
* 登录请求处理
*/
@CrossOrigin
@PostMapping("/login")
public Result login(@Validated @RequestBody LoginDto loginDto, HttpServletResponse response) {
User user = userService.getOne(new QueryWrapper<User>().eq("username", loginDto.getUsername()));
Assert.notNull(user, "用户名或密码错误");
if (!user.getPassword().equals(SecureUtil.md5(loginDto.getPassword()))) {
return Result.fail("用户名或密码错误!");
}
if(user.getStatus()==0){
return Result.fail("账户已被禁用");
}
String jwt = jwtUtils.generateToken(user.getId(),user.getUsername());
response.setHeader("Authorization", jwt);
response.setHeader("Access-Control-Expose-Headers", "Authorization");
// 用户可以另一个接口
return Result.succ(MapUtil.builder()
.put("id", user.getId())
.put("username", user.getUsername())
.put("avatar", user.getAvatar())
.put("email", user.getEmail())
.put("role", user.getRole())
.map()
);
}
/**
* 登出请求处理
*/
@GetMapping("/logout")
@RequiresAuthentication
public Result logout() {
SecurityUtils.getSubject().logout();
return Result.succ("退出成功");
}
}
package com.danli.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.danli.common.lang.vo.UserInfo;
import com.danli.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
*
* Mapper 接口
*
*
* @author fanfanli
* @date 2021-04-08
*/
@Mapper
@Repository
public interface UserMapper extends BaseMapper<User> {
/**
* 获取用户部分信息list
*/
@Select("select id, nickname, username, avatar, email, status, create_time, update_time, role from user order by create_time desc")
List<UserInfo> getUserInfo();
}
package com.danli.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.danli.common.lang.vo.UserInfo;
import com.danli.entity.User;
import java.util.List;
/**
* 服务类
*
* @author fanfanli
* @date 2021/4/5
*/
public interface UserService extends IService<User> {
/**
* 查询所有用户(只含有部分信息)
*
* @return 用户(只含有部分信息)list
*/
List<UserInfo> getUserInfoList();
}
package com.danli.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.danli.common.lang.vo.UserInfo;
import com.danli.entity.User;
import com.danli.mapper.UserMapper;
import com.danli.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 服务实现类
*
* @author fanfanli
* @date 2021-04-08
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Autowired
UserMapper userMapper;
/**
* 查询所有用户(只含有部分信息)
*
* @return 用户(只含有部分信息)list
*/
@Override
public List<UserInfo> getUserInfoList(){
List<UserInfo> userInfos = userMapper.getUserInfo();
return userInfos;
}
}