springboot记录三,已成功集成shiro和mybatis
1.引入maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- spring web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- freemarker -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
<version>1.5.8.RELEASE</version>
</dependency>
<!-- 数据库 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.0</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.8</version>
</dependency>
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
2.SQL脚本
在我的github地址里面有,整个项目和SQL
3. sql解析
1. 一个用户有一个角色,用户表里面有role_id
2. login代表登陆时验证的用户,user_name作为中文名称,password是MD5加密后的密码
3. salt是盐,我这里的加密是经过5次加密算出来的密码,根据当前时间的出来的盐
4. 一个角色有N个权限,分别是sys_role_permission里面的一条记录
5. 权限表sys_permission只有permission_name parent_id 和permission_code有意义
4. 业务代码
开始写dao mapper service controller,具体的我不列了,可以看源码
5. Shiro相关配置
其实关于shiro的共有两个大的配置,一个是ShiroConfiguration一个是自定义的MyRealm
1. 先编写自定义的MyRealm
package com.pf.org.cms.common;
import com.pf.org.cms.configuration.ShiroConfiguration;
import com.pf.org.cms.hcg.system.bean.PermissionDO;
import com.pf.org.cms.hcg.system.bean.UserDO;
import com.pf.org.cms.hcg.system.service.PermissionService;
import com.pf.org.cms.hcg.system.service.UserNewService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @Auther: pf
* @Date: 2017/12/12 19:29
* @Description: 认证和授权具体实现
* 因为shiro并不知道你登陆认证的具体逻辑和授权的具体逻辑,所以需要用户自己实现,继承AuthorizingRealm,
* 实现doGetAuthorizationInfo(授权)和doGetAuthenticationInfo(登陆认证)两个抽象方法
*/
public class MyRealm extends AuthorizingRealm {
@Autowired
private UserNewService userNewService;
@Autowired
private PermissionService permissionService;
private static final Logger log = LoggerFactory.getLogger(MyRealm.class);
/**
* 为当前subject授权
*
* @param principalCollection
* @return AuthorizationInfo
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
log.info("------------------授权");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
String loginName = (String) principalCollection.getPrimaryPrincipal();
log.info("==========shiro 授权" + loginName);
UserDO userDO = userNewService.getUserByLoginName(loginName);
List<PermissionDO> permission = permissionService.findByUser(userDO);
for (PermissionDO p : permission) {
info.addStringPermission(p.getPermissionCode());
log.info("===p.getPermissionCode()" + p.getPermissionCode());
}
return info;
}
/**
* 认证登陆subject身份
*
* @param token
* @return AuthenticationInfo
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
log.info("===============执行了认证方法");
//认证只做了【查询数据库 用户是否存在】
UserDO userDO = null;
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
String loginName = usernamePasswordToken.getUsername();
log.info("====UserRealm:token.getUsername():" + loginName);
userDO = userNewService.getUserByLoginName(loginName);
if (userDO == null) {
throw new UnknownAccountException("用户不存在!");
}
Object principle = loginName;
String credentials = userDO.getPassword();
String realmName = getName();
ByteSource salt = ByteSource.Util.bytes(userDO.getSalt());
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principle, credentials,salt,realmName);
return info;
}
public static void main(String[] args) { //====生成MD5加密后的: loginName+ salt.
String hashAlgorithName = "MD5";
String password = "wanwan";
int hashIterations2 = 5;//加密次数
ByteSource credentialsSalt = ByteSource.Util.bytes("1541123920660");
Object obj = new SimpleHash(hashAlgorithName, password, credentialsSalt, hashIterations2);
System.out.println(obj);
}
}
5.2 编写ShiroConfiguration
package com.pf.org.cms.configuration;
import com.pf.org.cms.common.MyRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import java.util.LinkedHashMap;
/**
* @Auther: pf
* @Date: 2017/12/12 19:34
* @Description: shiro配置组件
*/
@Configuration
public class ShiroConfiguration {
/**
* 密码校验规则HashedCredentialsMatcher
* 这个类是为了对密码进行编码的 ,
* 防止密码在数据库里明码保存 , 当然在登陆认证的时候 ,
* 这个类也负责对form里输入的密码进行编码
* 处理认证匹配处理器:如果自定义需要实现继承HashedCredentialsMatcher
*/
@Bean("hashedCredentialsMatcher")
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
//指定加密方式为MD5
credentialsMatcher.setHashAlgorithmName("MD5");
//加密次数
credentialsMatcher.setHashIterations(5);
credentialsMatcher.setStoredCredentialsHexEncoded(true);
return credentialsMatcher;
}
@Bean("MyRealm")
@DependsOn("lifecycleBeanPostProcessor")//可选
public MyRealm MyRealm(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher matcher) {
MyRealm MyRealm = new MyRealm();
MyRealm.setAuthorizationCachingEnabled(false);
MyRealm.setCredentialsMatcher(matcher);
return MyRealm;
}
/**
* 定义安全管理器securityManager,注入自定义的realm
*
* @param MyRealm
* @return
*/
@Bean("securityManager")
public SecurityManager securityManager(@Qualifier("MyRealm") MyRealm MyRealm) {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(MyRealm);
return manager;
}
/**
* 定义shiroFilter过滤器并注入securityManager
*
* @param securityManager
* @return
*/
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager securityManager) {
ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
filterFactoryBean.setSecurityManager(securityManager);
// 配置登录的url
filterFactoryBean.setLoginUrl("/authenticate");
//登录成功的url
filterFactoryBean.setSuccessUrl("/home");
// 配置未授权跳转页面
filterFactoryBean.setUnauthorizedUrl("/errorPage/403");
// 配置访问权限
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/css/**", "anon"); // 表示可以匿名访问
filterChainDefinitionMap.put("/fonts/**", "anon");
filterChainDefinitionMap.put("/imgs/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/auth/**", "anon");
filterChainDefinitionMap.put("/errorPage/**", "anon");
filterChainDefinitionMap.put("/demo/**", "anon");
filterChainDefinitionMap.put("/swagger-*/**", "anon");
filterChainDefinitionMap.put("/swagger-ui.html/**", "anon");
filterChainDefinitionMap.put("/webjars/**", "anon");
filterChainDefinitionMap.put("/v2/**", "anon");
// 表示admin权限才可以访问
filterChainDefinitionMap.put("/admin/**", "roles[admin]");
// 表示需要认证才可以访问
filterChainDefinitionMap.put("/*", "authc");
filterChainDefinitionMap.put("/**", "authc");
filterChainDefinitionMap.put("/*.*", "authc");
filterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return filterFactoryBean;
}
/**
* Spring的一个bean , 由Advisor决定对哪些类的方法进行AOP代理 .
*
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
creator.setProxyTargetClass(true);
return creator;
}
/**
* 配置shiro跟spring的关联
*
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
/**
* lifecycleBeanPostProcessor是负责生命周期的 , 初始化和销毁的类
* (可选)
*/
@Bean("lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
}
6. controller测试
注意:我刚才在ShiroConfiguration里面设置的拦截url是authenticate
// 配置登录的url
filterFactoryBean.setLoginUrl("/authenticate");
所以controller模拟的时候用这个路径测试就行了
@Controller
public class UserController {
@Autowired
UserNewService userNewService;
@Autowired
PermissionService permissionService;
private static final Logger log = LoggerFactory.getLogger(UserController.class);
//用户用户名和密码MD5加密后的验证
@RequestMapping(value = "authenticate", method = RequestMethod.GET)
@ResponseBody
public String authenticate(@RequestParam("loginName") String loginName, @RequestParam("password") String password,
HttpServletRequest request, HttpSession session, HttpServletResponse response) {
//把前端输入的username和password封装为token
UsernamePasswordToken token = new UsernamePasswordToken(loginName, password);
// 认证身份
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
session.setAttribute("user", subject.getPrincipal());
log.info("登陆成功");
return "success";
} catch (Exception e) {
log.info("登陆失败");
return "false";
}
}
}
7. Postman测试
模拟MD5加密在数据库中添加数据
public static void main(String[] args) {
//====生成MD5加密后的: loginName+ salt.
String salt = Long.toString(System.currentTimeMillis());
System.out.println("保存在数据库中的盐"+salt);
ByteSource byteSource = ByteSource.Util.bytes(salt);
String hashAlgorithName = "MD5";
String password = "wanwan";
int hashIterations2 = 5;//加密次数
Object obj = new SimpleHash
(hashAlgorithName, password, byteSource, hashIterations2);
System.out.println("保存在数据库中的密码:"+obj.toString());
}
在用户表里面插入一条数据值分别是:上面生成的login_name 和password和salt即可
用下面这个URL直接在浏览器中测试即可
http://localhost:8080/authenticate?loginName=wanwan&password=wanwan