本项目通过SpringBoot整合了shiro框架 使用MybatisPlus代码生成器生成简单的代码,项目包含shiro动态授权以及认证 ,rememberMe记住功能
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.1.6.RELEASE
com.example
springboot-shiro
0.0.1-SNAPSHOT
springboot-shiro
Demo project for Spring Boot Shiro
1.8
org.springframework.boot
spring-boot-starter-thymeleaf
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
mysql
mysql-connector-java
5.1.46
com.baomidou
mybatis-plus-boot-starter
3.1.0
com.baomidou
mybatis-plus-generator
3.1.0
org.apache.velocity
velocity-engine-core
2.1
junit
junit
4.12
org.apache.shiro
shiro-spring
1.4.0
org.springframework.boot
spring-boot-devtools
true
net.sf.ehcache
ehcache-core
2.4.8
org.apache.shiro
shiro-ehcache
1.4.0
com.alibaba
fastjson
1.2.13
org.springframework
spring-context-support
5.1.8.RELEASE
org.springframework.boot
spring-boot-maven-plugin
true
server.port=80
spring.mvc.static-path-pattern=/static/**
spring.resources.static-locations=classpath:/static/
#mysql
spring.datasource.url=jdbc:mysql://localhost:3306/crm?useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#mybatis-plus
mybatis-plus.type-aliases-package=com.example.leilei.entity
#热部署
#设置开启热部署
spring.devtools.restart.enabled=true
#页面不加载缓存,修改即时生效
spring.freemarker.cache=false
@SpringBootApplication
@MapperScan(basePackages = "com.example.leilei.mapper")
public class SpringbootShiroApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootShiroApplication.class, args);
}
}
合理使用工具可以帮我减少开发时间
public class CodeGenerator {
public static void main(String[] args) throws InterruptedException {
//用来获取Mybatis-Plus.properties文件的配置信息
ResourceBundle rb = ResourceBundle.getBundle("springboot-shiro");
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
gc.setOutputDir(rb.getString("OutputDir"));
gc.setFileOverride(true);
gc.setActiveRecord(true);// 开启 activeRecord 模式
gc.setEnableCache(false);// XML 二级缓存
gc.setBaseResultMap(true);// XML ResultMap
gc.setBaseColumnList(false);// XML columList
gc.setAuthor(rb.getString("author"));
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setDbType(DbType.MYSQL);
dsc.setTypeConvert(new MySqlTypeConvert());
dsc.setDriverName("com.mysql.jdbc.Driver");
dsc.setUsername(rb.getString("jdbc.user"));
dsc.setPassword(rb.getString("jdbc.pwd"));
dsc.setUrl(rb.getString("jdbc.url"));
mpg.setDataSource(dsc);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
// strategy.setTablePrefix(new String[] { "t_" });// 此处可以修改为您的表前缀
strategy.setNaming(NamingStrategy.underline_to_camel);// 表名生成策略
strategy.setInclude(new String[]{"real_eseate"}); // 需要生成的表
mpg.setStrategy(strategy);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setParent(rb.getString("parent"));
pc.setController("controller");
pc.setService("service");
pc.setServiceImpl("service.impl");
pc.setEntity("domain");
pc.setMapper("mapper");
mpg.setPackageInfo(pc);
// 注入自定义配置,可以在 VM 中使用 cfg.abc 【可无】
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
}
};
List focList = new ArrayList();
// 调整 xml 生成目录演示
focList.add(new FileOutConfig("/templates/mapper.xml.vm") {
@Override
public String outputFile(TableInfo tableInfo) {
return rb.getString("OutputDirXml")+ "/cn/leilei/mapper/" + tableInfo.getEntityName() + "Mapper.xml";
}
});
// 调整 query 生成目录
/* focList.add(new FileOutConfig("/templates/query.java.vm") {
@Override
public String outputFile(TableInfo tableInfo) {
return rb.getString("OutputDomainDir")+ "/cn/leilei/query/" + tableInfo.getEntityName() + "Query.java";
}
});*/
// 调整 domain 生成目录
focList.add(new FileOutConfig("/templates/entity.java.vm") {
@Override
public String outputFile(TableInfo tableInfo) {
return rb.getString("OutputDomainDir")+ "/cn/leilei/domain/" + tableInfo.getEntityName() + ".java";
}
});
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
// 自定义模板配置,可以 copy 源码 mybatis-plus/src/main/resources/templates 下面内容修改,
// 放置自己项目的 src/main/resources/templates 目录下, 默认名称一下可以不配置,也可以自定义模板名称
TemplateConfig tc = new TemplateConfig();
//tc.setController("/templates/controller.java.vm");
// 如上任何一个模块如果设置 空 OR Null 将不生成该模块。
tc.setEntity(null);
tc.setXml(null);
mpg.setTemplate(tc);
// 执行生成
mpg.execute();
}
}
springboot-shiro.perproties
#输出路径
OutputDir=E://EEworkspac//springbootshiro//springboot-shiro//src//main//java
#query和domain的生成路径
OutputDomainDir=E://EEworkspac//springbootshiro//springboot-shiro//src//main//java
#作者
author=lei
#数据源
jdbc.user=root
jdbc.pwd=root
jdbc.url=jdbc:mysql:///crm?useUnicode=true&characterEncoding=utf8
#包配置
parent=cn.leilei
#xml的生成的resources目录
OutputDirXml=E://EEworkspac//springbootshiro//springboot-shiro//src//main//resources
这个shiro生命周期的Bean一定要单独配置
/**
* 这个shiro生命周期的Bean一定要单独配置
*/
@Configuration
public class ShiroLifecycleBeanPostProcessorConfig {
/**
* Shiro生命周期处理器
*
* @return
*/
@Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
}
public class MyRealm extends AuthorizingRealm {
@Autowired
private IEmployeeService employeeService;
@Autowired
private IPermissionService permissionService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
Employee employee = (Employee) principalCollection.getPrimaryPrincipal();
Long empId = employee.getId();
//根据员工id查询员工权限
List permissions = permissionService.getByEmpId(empId);
//将查询出的权限交给shiro
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
for (Permission permission : permissions) {
info.addStringPermission(permission.getSn());
}
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String username = token.getUsername();
//根据用户名查询用户
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.eq("username",username);
Employee employee = employeeService.getOne(wrapper);
if(employee==null){
throw new UnknownAccountException(username);
}
//封装info对象
return new SimpleAuthenticationInfo(employee,employee.getPassword(), ByteSource.Util.bytes(MD5Utils.SALT),getName());
}
}
授权方法需要连表查询
根据elmplyee 查询到permission表
简单的写了下,但在实际开发中sql语句不要使用 *
目前项目中包含认证,授权,rememberMe
package com.example.leilei.config;
import com.example.leilei.entity.Permission;
import com.example.leilei.service.IEmployeeService;
import com.example.leilei.service.IPermissionService;
import com.example.leilei.shiro.realm.MyRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@Configuration
@AutoConfigureAfter(ShiroLifecycleBeanPostProcessorConfig.class)//配置Bean加载的先后顺序
public class ShiroConfig {
@Autowired
private IPermissionService permissionService;
/**
* SecurityManager核心对象Bean
* @return
*/
@Bean(name = "securityManager")
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//注入Realm
securityManager.setRealm(myRealm());
//注入记住我管理器
securityManager.setRememberMeManager(rememberMeManager());
return securityManager;
}
/**
* 凭证比较器-加密加盐加次数
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");//加密算法
hashedCredentialsMatcher.setHashIterations(10);//加密次数
return hashedCredentialsMatcher;
}
/**
* cookie对象;
* rememberMeCookie()方法是设置Cookie的生成模版,比如cookie的name,cookie的有效时间等等。
* @return
*/
@Bean
public SimpleCookie rememberMeCookie(){
//System.out.println("ShiroConfiguration.rememberMeCookie()");
//这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
//
simpleCookie.setMaxAge(259200);
return simpleCookie;
}
/**
* cookie管理对象;
* rememberMeManager()方法是生成rememberMe管理器,而且要将这个rememberMe管理器设置到securityManager中
* @return
*/
@Bean
public CookieRememberMeManager rememberMeManager(){
//System.out.println("ShiroConfiguration.rememberMeManager()");
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
//rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
cookieRememberMeManager.setCipherKey(Base64.decode("2AvVhdsgUs0FSA3SDFAdag=="));
return cookieRememberMeManager;
}
/**
* 自定义的Realm
* @return
*/
@Bean
public MyRealm myRealm(){
MyRealm myRealm = new MyRealm();
//设置密码加密凭证,登录时会对密码进行加密匹配
myRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myRealm;
}
/**
*过滤器配置 过滤所有权限
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//配置过滤器
Map filterChainDefinitionMap = new LinkedHashMap();
filterChainDefinitionMap.put("/static/**", "anon"); //anon代表资源直接放行
filterChainDefinitionMap.put("/logout", "logout"); //shiro的退出方法,会注销自己的认证
//权限拦截,查出所有权限
List permissions = permissionService.list();
for (Permission permission : permissions) {
filterChainDefinitionMap.put(permission.getUrl(),"perms["+permission.getSn()+"]");
}
filterChainDefinitionMap.put("/**", "authc");
//当访问需要认证才能访问的资源,如果没有认证,则跳转到这个资源
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index");
//当访问需要授权才能访问的资源的时候,如果没有权限,则跳转到这个资源
shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
}
/**
* 这个类用来返回ajax对象,并且私有化字段,只能通过构造方法赋值
*
*/
public class AjaxResult {
private Boolean success = true;
private String msg;
private AjaxResult() {
}
/**
* 返回ajax
* @return 没有错误就返回这个
*/
public static AjaxResult success(){
return new AjaxResult();
}
/**
* 返回ajax
* @param msg 错误信息
* @return 有错误就返回这个
*/
public static AjaxResult error(String msg){
AjaxResult ajaxResult = success();;
ajaxResult.setSuccess(false);
ajaxResult.setMsg(msg);
return ajaxResult;
}
public Boolean getSuccess() {
return success;
}
private void setSuccess(Boolean success) {
this.success = success;
}
public String getMsg() {
return msg;
}
private void setMsg(String msg) {
this.msg = msg;
}
}
public class SessionUtil {
public static final String LOGINSESSION = "loginuser";
//将登陆对象存入域对象之中
public static void setSession(Employee employee){//将登陆用户存入域对象
Subject subject = SecurityUtils.getSubject();//获取登陆对象
subject.getSession().setAttribute(LOGINSESSION,employee);
}
//Session中获取当前登陆对象
public static Employee getSession(){//获取登陆对象域对象信息
Subject subject = SecurityUtils.getSubject();//获取登陆对象
return (Employee) subject.getSession().getAttribute(LOGINSESSION);
}
}
public class MD5Utils {
public static final String SALT = "fm";
public static final int ITERATIONS = 10;
/**
* 加密
* @param source
* @return
*/
public static String encrype(String source){
SimpleHash simpleHash = new SimpleHash("MD5",source,SALT,ITERATIONS);
return simpleHash.toString();
}
public static void main(String[] args) {
System.out.println(encrype("admin"));
}
}
@Controller
public class LoginController {
/**
* 跳转到登录页面
* @return
*/
@RequestMapping(value = "/login",method = RequestMethod.GET)
public String login(){
Subject subject = SecurityUtils.getSubject();
//判断当前用户是否有使用rememberMe
if (subject.isRemembered()){
return "main";
//判断当前用户是否登录
}else if(subject.isAuthenticated()){
return "main";
}
return "login";
}
/**
* 登录请求
* @param employee
* @return
*/
@RequestMapping(value = "/login",method = RequestMethod.POST)
@ResponseBody
public AjaxResult login(@RequestBody Employee employee){
//尝试获取获取用户信息
Subject subject = SecurityUtils.getSubject();
/*Boolean rememberMe = true;*/
//将输入的账户密码封装为一个tocken对象
UsernamePasswordToken token = new UsernamePasswordToken(employee.getUsername(),employee.getPassword(),employee.getRememberMe());
try {
//使用封装的tocken对象尝试通过shiro完成认证---->会调用自定义MyRealm类的AuthenticationInfo方法,尝试认证,
if(!subject.isAuthenticated()){//判断当前用户是否登录 布尔值 取反
subject.login(token);//认证登陆
}
//将登陆的用户信息存入域对象之中
Employee logiuser = ((Employee) subject.getPrincipal());//获取当前用户
SessionUtil.setSession(logiuser);
return AjaxResult.success();
} catch (UnknownAccountException e) {
e.printStackTrace();
return AjaxResult.error("账户不存在");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
return AjaxResult.error("密码错误");
} catch (AuthenticationException e) {
e.printStackTrace();
return AjaxResult.error("未知错误,检查后台");
}
}
//退出登录
@RequestMapping(value = "/logout")
public String logout(HttpServletRequest request, HttpServletResponse response) {
return "redirect:/logout";
}
}
前端所用的静态资源放在 resource/statc下
前端所用的页面放在 resource/templates下
Title
员工登录
记住密码
登录
其他页面按着编写就好 最终采用我的源码会完成动态授权以及认证以及rememberMe的功能实现