最近在学shiro以及mybatisplus代码生成器,在搭建过程中多多少少也踩了一些坑。留作纪念吧。
shiro
Shiro是一个强大的简单易用的Java安全框架,主要用来更便捷的认证,授权,加密,会话管理。可以看出shiro除了基本的认证,授权,会话管理,加密之外,还有许多额外的特性。
shiro主要架构
subject:一般是用户,任何与应用交互的“用户”。
securitymanager:安全管理器(管理realms,sessionmanager,ssiondao,cachemanager),shiro的核心。
realms:Realms作为Shiro和你的应用的连接桥,当需要与安全数据交互的时候,像用户账户,或者访问控制,Shiro就从一个或多个Realms中查找。
搭建步骤
-
导入shiro依赖
因为使用了redis存放sessionId所以也导入了shiro-redis依赖。
<!-- shiro--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.3.2</version> </dependency> <!-- shiro redis--> <dependency> <groupId>org.crazycake</groupId> <artifactId>shiro-redis</artifactId> <version>3.0.0</version> </dependency>
-
配置shiroConfiguration
这是shiro的配置类,使用 LifecycleBeanPostProcessor Bean会在创建sessionid时报null异常,注释掉发现对权限注解的支持没有影响。
package hnu.boot.config; import org.apache.shiro.mgt.DefaultSecurityManager; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.LifecycleBeanPostProcessor; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.crazycake.shiro.RedisCacheManager; import org.crazycake.shiro.RedisManager; import org.crazycake.shiro.RedisSessionDAO; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import java.util.HashMap; import java.util.Map; /** * @Classname ShiroConfiguration * @Description TODO * @Date 2019/10/23 15:32 * @Created by yz */ @Configuration public class ShiroConfiguration { //1.realm @Bean public CustomRealm getRealm(){ return new CustomRealm(); } //2.securityManagerment @Bean public SecurityManager getSecurityManager(CustomRealm realm){ DefaultSecurityManager securityManager=new DefaultWebSecurityManager(); securityManager.setRealm(realm); //将自定义的sessionmanager注册到securitymanager中 securityManager.setSessionManager(sessionManager()); //将自定义的cachemanager注册到securitymanager securityManager.setCacheManager(redisCacheManager()); return securityManager; } //3.shiro过滤器工厂 @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){ ShiroFilterFactoryBean filterFactoryBean=new ShiroFilterFactoryBean(); filterFactoryBean.setSecurityManager(securityManager); filterFactoryBean.setLoginUrl("/autherror?code=1"); filterFactoryBean.setUnauthorizedUrl("/autherror?code=2"); Map<String,String> filterMap=new HashMap<>(); //当前请求可以匿名访问 filterMap.put("/home","anon"); //认证后才能访问 filterMap.put("/user/**","authc"); filterFactoryBean.setFilterChainDefinitionMap(filterMap); return filterFactoryBean; } @Value("${spring.redis.host}") private String host; @Value("${spring.redis.port}") private int port; /** * redis控制器 操作redis * @return */ public RedisManager redisManager(){ RedisManager redisManager=new RedisManager(); redisManager.setHost(host); redisManager.setPort(port); return redisManager; } /** sessionDao*/ public RedisSessionDAO redisSessionDAO(){ RedisSessionDAO redisSessionDAO=new RedisSessionDAO(); redisSessionDAO.setRedisManager(redisManager()); return redisSessionDAO; } /** * sessionManager * @return */ public DefaultWebSessionManager sessionManager(){ CustomSessionManager sessionManager=new CustomSessionManager(); sessionManager.setSessionDAO(redisSessionDAO()); return sessionManager; } /** * 缓存管理器cacha manager (redis) * @return */ public RedisCacheManager redisCacheManager(){ RedisCacheManager cacheManager=new RedisCacheManager(); cacheManager.setRedisManager(redisManager()); return cacheManager; } //4.开启对注解的支持 // @Bean // public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){ // return new LifecycleBeanPostProcessor(); // } // @DependsOn({"lifecycleBeanPostProcessor"}) @Bean public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){ DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); advisorAutoProxyCreator.setProxyTargetClass(true); return advisorAutoProxyCreator; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){ AuthorizationAttributeSourceAdvisor advisor=new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; } }
-
写realm
其实就是重写认证与授权方法。
package hnu.boot.config; import com.baomidou.mybatisplus.mapper.EntityWrapper; import hnu.boot.readboy.entity.User; import hnu.boot.readboy.service.impl.UserServiceImpl; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.crypto.hash.Md5Hash; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.springframework.beans.factory.annotation.Autowired; import java.util.HashSet; import java.util.Set; /** * 自定义的realm */ public class CustomRealm extends AuthorizingRealm { @Override public void setName(String name) { super.setName("customRealm"); } @Autowired private UserServiceImpl userService; /** * 权限认证 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { User user = (User) principalCollection.getPrimaryPrincipal();//得到唯一的安全数据 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); Set<String> roles = new HashSet<>(); Set<String> perms = new HashSet<>(); if (user.getId().equals(1)){ roles.add("admin"); } roles.add("user"); info.setStringPermissions(perms); info.setRoles(roles); return info; } /** * 登录校验 * */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken upToken = (UsernamePasswordToken) authenticationToken; String username = upToken.getUsername(); String password = new String( upToken.getPassword()); User user = userService.selectOne(new EntityWrapper<User>().eq("username",username)); if(user != null && user.getPassword().equals(password)) { SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),this.getName()); return info; } return null; } }
4.测试权限校验
package hnu.boot.readboy.controller;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresGuest;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
* 前端控制器
* </p>
*
* @author yzw
* @since 2019-10-23
*/
@Slf4j
@RestController
public class UserController {
@RequestMapping(value = "/login",method = RequestMethod.GET)
public String login(String username,String password){
try {
password=new Md5Hash(password,username,3).toString();
UsernamePasswordToken token=new UsernamePasswordToken(username,password);
Subject subject=SecurityUtils.getSubject();
String sid= (String) subject.getSession().getId();
subject.login(token);
return "success "+sid;
}catch (Exception e){
e.printStackTrace();
return "error.";
}
}
@RequestMapping(value = "/home",method = RequestMethod.GET)
@RequiresRoles("user")
public String home(){
Subject subject=SecurityUtils.getSubject();
subject.getSession();
return "home";
}
@RequestMapping(value = "/user/admin",method = RequestMethod.GET)
@RequiresRoles("admin")
public String Admin(){
return "admin";
}
@RequestMapping(value = "/autherror")
public String autherror(int code){
return code==1?"还未登录":"无权限";
}
}
mybatis plus代码生成器
-
导入坐标
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>2.2.0</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-core</artifactId> <version>2.2.0</version> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.1</version> </dependency> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.29</version> </dependency> <dependency> <groupId>com.ibeetl</groupId> <artifactId>beetl</artifactId> <version>3.0.13.RELEASE</version> </dependency>
实际使用过程中貌似要用3.+版本。这里使用2.+是因为可以使用entitywrapper。
-
修改application.yml
spring: application: name: HNUSBoot datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://*******/****?userUnicode=true&characterEncoding=UTF8&useSSL=false&useAffectedRows=true&useLegacyDatetimeCode=false&serverTimezone=CTT&useJDBCCompliantTimezoneShift=true username: readboy password: ****** redis: host: ******* port: 6379 mybatis-plus: mapper-locations: classpath*:xml/*.xml global-config: id-type: 3
-
编写CodeGenerator(官方例子)
package hnu.boot.config; import com.baomidou.mybatisplus.exceptions.MybatisPlusException; 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.NamingStrategy; import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; import org.apache.commons.lang3.StringUtils; import javax.xml.crypto.Data; import java.util.ArrayList; import java.util.List; import java.util.Scanner; /** * @Classname CodeGenerator * @Description TODO * @Date 2019/10/22 22:21 * @Created by yz */ // 演示例子,执行 main 方法控制台输入模块表名回车自动生成对应项目目录中 public class CodeGenerator { private static String URL="jdbc:mysql:xxxx"; private static String USER_NAME="readboy"; private static String PASS_WORD="xxxxxxxx"; private static String DRIVER="com.mysql.cj.jdbc.Driver"; /** * <p> * 读取控制台内容 * </p> */ 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 = "E:/chenxuyuan/IDeaPj/boot"; gc.setOutputDir(projectPath + "/src/main/java"); gc.setAuthor("yzw"); gc.setOpen(false); // gc.setSwagger2(true); 实体属性 Swagger2 注解 mpg.setGlobalConfig(gc); // 数据源配置 DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl(URL); // dsc.setSchemaName("public"); dsc.setDriverName(DRIVER); dsc.setUsername(USER_NAME); dsc.setPassword(PASS_WORD); mpg.setDataSource(dsc); // 包配置 PackageConfig pc = new PackageConfig(); pc.setModuleName(scanner("模块名")); pc.setParent("hnu.boot"); mpg.setPackageInfo(pc); // 自定义配置 InjectionConfig cfg = new InjectionConfig() { @Override public void initMap() { // to do nothing } }; // 如果模板引擎是 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 的名称会跟着发生变化!!使用2.+的mybatisplus这里会报错,使用3.+就好了。。 return projectPath + "/src/main/resources/mapper/" + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML; } }); /* cfg.setFileCreate(new IFileCreate() { @Override public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) { // 判断自定义文件夹是否需要创建 checkDir("调用默认方法创建的目录"); return false; } }); */ cfg.setFileOutConfigList(focList); mpg.setCfg(cfg); // 配置模板 TemplateConfig templateConfig = new TemplateConfig(); // 配置自定义输出模板 //指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别 // templateConfig.setEntity("templates/entity2.java"); // templateConfig.setService(); // templateConfig.setController(); templateConfig.setXml(null); mpg.setTemplate(templateConfig); // 策略配置 StrategyConfig strategy = new StrategyConfig(); strategy.setNaming(NamingStrategy.underline_to_camel); strategy.setColumnNaming(NamingStrategy.underline_to_camel); // strategy.setSuperEntityClass("boot.common.BaseEntity"); strategy.setEntityLombokModel(true); // 公共父类 // strategy.setSuperControllerClass("boot.common.BaseController"); // 写于父类中的公共字段 // strategy.setSuperEntityColumns("id"); strategy.setInclude(scanner("表名,多个英文逗号分割").split(",")); strategy.setControllerMappingHyphenStyle(true); strategy.setTablePrefix(pc.getModuleName() + "_"); mpg.setStrategy(strategy); mpg.setTemplateEngine(new FreemarkerTemplateEngine()); mpg.execute(); } }
然后执行就好啦。项目代码地址
第一次写博客,很乱。希望对看到这篇文章的人提供一些帮助。