1.使用intellij新建springboot项目,编写pom文件,各部分的dependency作用都有说明
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.17</version>
</dependency>
<!--springBoot thymeleaf模板-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>1.5.1.RELEASE</version>
</dependency>
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<!--mysql connect-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.20</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-juli</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.39</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
<!--springBoot configuration-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
2.配置mybatis和mysql
mybatis配置文件
package com.shiro.demo.config;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
import java.util.Properties;
/**
* @author tracyclock 2017-12-08
* springboot集成mybatis的基本入口
* 1)创建数据源
* 2)创建SqlSessionFactory
*/
@Configuration
@MapperScan(basePackages="com.shiro.demo.mapper")
public class MyBatisConfig implements EnvironmentAware {
private Environment env;
@Override
public void setEnvironment(Environment environment) {
this.env = environment;
}
/**
* 创建数据源
* @Primary 该注解表示在同一个接口有多个实现类可以注入的时候,默认选择哪一个,而不是让@autowire注解报错
*/
@Bean
public DataSource getDataSource() throws Exception{
Properties props = new Properties();
props.put("driverClassName", env.getProperty("jdbc.driverClassName"));
props.put("url", env.getProperty("jdbc.url"));
props.put("username", env.getProperty("jdbc.username"));
props.put("password", env.getProperty("jdbc.password"));
return DruidDataSourceFactory.createDataSource(props);
}
/**
* 根据数据源创建SqlSessionFactory
*/
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource ds) throws Exception{
org.apache.ibatis.session.Configuration config = new org.apache.ibatis.session.Configuration();
//开启驼峰规则映射
config.setMapUnderscoreToCamelCase(true);
SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
fb.setConfiguration(config);
Interceptor[] interceptors = new Interceptor[]{new PagePlugin()};
fb.setPlugins(interceptors);
fb.setDataSource(ds);//指定数据源(这个必须有,否则报错)
//下边两句仅仅用于*.xml文件,如果整个持久层操作不需要使用到xml文件的话(只用注解就可以搞定),则不加
fb.setTypeAliasesPackage(env.getProperty("mybatis.typeAliasesPackage"));//指定基包
fb.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(env.getProperty("mybatis.mapperLocations")));//指定xml文件位置
return fb.getObject();
}
}
PagePlugin mybatis分页插件
package com.shiro.demo.config;
import com.shiro.demo.domain.Pager;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.RowBounds;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
/**
* @author tracyclock 2017-12-08
* 分页插件
*/
@Intercepts({@Signature(
type = StatementHandler.class,
method = "prepare",
args = {Connection.class,Integer.class}
)})
public class PagePlugin implements Interceptor {
public PagePlugin() {
}
public Object intercept(Invocation invocation) throws Throwable {
Object target = invocation.getTarget();
if (target instanceof StatementHandler) {
StatementHandler statementHandler = (StatementHandler)target;
MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
RowBounds rowBounds = null;
try {
rowBounds = (RowBounds)metaStatementHandler.getValue("delegate.rowBounds");
if (rowBounds == null || rowBounds == RowBounds.DEFAULT) {
return invocation.proceed();
}
} catch (Exception var11) {
invocation.getTarget();
}
BoundSql boundSql = (BoundSql)metaStatementHandler.getValue("delegate.boundSql");
String originalSql = boundSql.getSql();
if (rowBounds instanceof Pager) {
MappedStatement mappedStatement = (MappedStatement)metaStatementHandler.getValue("delegate.mappedStatement");
Connection connection = (Connection)invocation.getArgs()[0];
Pager page = (Pager)rowBounds;
page = this.count(originalSql, connection, mappedStatement, boundSql, page);
if (page.getTotal() <= 0L) {
return invocation.proceed();
}
originalSql = this.buildPageSql(originalSql, page.getPageNumber(), page.getSize());
}
metaStatementHandler.setValue("delegate.boundSql.sql", originalSql);
}
return invocation.proceed();
}
private String buildPageSql(String originalSql, int offset, int limit) {
StringBuilder sql = new StringBuilder(originalSql);
sql.append(" LIMIT ").append(offset).append(",").append(limit);
return sql.toString();
}
private Pager count(String sql, Connection connection, MappedStatement mappedStatement, BoundSql boundSql, Pager page) {
String sqlUse = sql;
int order_by = sql.toUpperCase().lastIndexOf("ORDER BY");
if (order_by > -1) {
sqlUse = sql.substring(0, order_by);
}
StringBuffer countSql = new StringBuffer();
countSql.append("SELECT COUNT(1) AS TOTAL FROM (").append(sqlUse).append(") A");
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
pstmt = connection.prepareStatement(countSql.toString());
BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql.toString(), boundSql.getParameterMappings(), boundSql.getParameterObject());
ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, boundSql.getParameterObject(), countBS);
parameterHandler.setParameters(pstmt);
rs = pstmt.executeQuery();
int total = 0;
if (rs.next()) {
total = rs.getInt(1);
}
page.setTotal((long)total);
} catch (SQLException var22) {
var22.printStackTrace();
} finally {
try {
rs.close();
pstmt.close();
} catch (SQLException var21) {
var21.printStackTrace();
}
}
return page;
}
public Object plugin(Object target) {
return target instanceof StatementHandler ? Plugin.wrap(target, this) : target;
}
@Override
public void setProperties(Properties properties) {
}
}
Pager
package com.shiro.demo.domain;
import org.apache.ibatis.session.RowBounds;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import java.io.Serializable;
public class Pager<T> extends RowBounds implements Pageable, Serializable {
private long total;
private T data;
private int pageNo = 1; //vision的page是从1开始的
private int pageSize = 10;
Pager() {
}
public int getPageNo() {
return pageNo;
}
public void setPageNo(int pageNo) {
this.pageNo = pageNo;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public Pager(int pageNo, int pageSize) {
this.pageNo = pageNo;
this.pageSize = pageSize;
}
public void setData(T data) {
this.data = data;
}
public T getData() {
return this.data;
}
public long getTotal() {
return this.total;
}
public void setTotal(long total) {
this.total = total;
}
public int getPage() {
return this.pageNo;
}
public void setPage(int page) {
this.pageNo = pageNo;
}
public int getSize() {
return this.pageSize;
}
public void setSize(int size) {
this.pageSize = pageSize;
}
public int getPageNumber() {
return (this.pageNo - 1) * this.pageSize;
}
public int getPageSize() {
return this.pageSize;
}
public int getOffset() {
return 0;
}
public Sort getSort() {
return null;
}
public Pageable next() {
return new Pager(this.pageNo + 1, this.pageSize);
}
public Pageable previousOrFirst() {
return null;
}
public Pageable first() {
return null;
}
public boolean hasPrevious() {
return false;
}
@Override
public String toString() {
return "Pager{" +
"total=" + total +
", data=" + data +
", pageNo=" + pageNo +
", pageSize=" + pageSize +
'}';
}
}
jdbc.driverClassName: com.mysql.jdbc.Driver
jdbc.url: jdbc:mysql://127.0.0.1/test?useUnicode=true&characterEncoding=UTF-8
jdbc.username: root
jdbc.password:
jdbc.initialSize: 20
jdbc.minIdle: 20
jdbc.maxActive: 100
mybatis.typeAliasesPackage: com.shiro.demo.domain
mybatis.mapperLocations: classpath:mybatis-mappers/*.xml
# shiro 配置不会被拦截的路径
# 具体的退出代码Shiro已经替我们实现了
# 过滤链定义,从上向下顺序执行,一般将/**放在最为下边
# authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
shiroFilter:
filterChainDefinitionMap:
/shiro/loginAction: anon
/shiro/index: anon
# 模板配置
# 这个开发配置为false,避免改了模板还要重启服务器
spring.thymeleaf.cache: false
# 这个是配置模板路径的,默认就是templates,可不用配置
spring.thymeleaf.prefix=classpath: /templates/
# 这个可以不配置,检查模板位置
spring.thymeleaf.check-template-location: true
# 下面3个不做解释了,可以不配置
spring.thymeleaf.suffix: .html
spring.thymeleaf.encoding: UTF-8
spring.thymeleaf.content-type: text/html
# 模板的模式
spring.thymeleaf.mode: HTML5
3.编写实体类User,Role,Permission
User
package com.shiro.demo.domain;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* @author tracyclock 2017-12-08
*/
@Data
public class User implements Serializable {
private Long id;
private String name;
private String password;
private String email;
private Date createTime;
private Date updateTime;
private Date lastLoginTime;
private Integer status;
private String salt;
}
Role
package com.shiro.demo.domain;
import lombok.Data;
import java.io.Serializable;
/**
* @author tracyclock 2017-12-08
*/
@Data
public class Role implements Serializable {
private Long id;
private String name;
private String type;
private Integer status; // 是否可用,0可用,-1不可用
}
Permission
package com.shiro.demo.domain;
import lombok.Data;
import java.io.Serializable;
/**
* @author tracyclock 2017-12-08
*/
@Data
public class Permission implements Serializable{
private Long id;
private String url;
private String name;
private String resourceType;//资源类型,[menu|button]
private String permission; //权限字符串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:view
private Long parentId; //父编号
private String parentIds; //父编号列表
private Integer status ;
}
CREATE DATABASE /*!32312 IF NOT EXISTS*/`test` /*!40100 DEFAULT CHARACTER SET latin1 */;
USE `test`;
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`url` varchar(256) DEFAULT NULL COMMENT 'url地址',
`name` varchar(64) DEFAULT NULL COMMENT 'url描述',
`status` int(11) DEFAULT '0' COMMENT '0 可用 -1删除',
`parent_id` bigint(20) DEFAULT NULL,
`parent_ids` varchar(100) DEFAULT NULL,
`permission` varchar(100) DEFAULT NULL,
`resource_type` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
insert into `permission`(`id`,`url`,`name`,`status`,`parent_id`,`parent_ids`,`permission`,`resource_type`) values (1,'/shiro/list','用户管理',0,0,'0/','shiro:view','menu'),(2,'/shiro/add','用户添加',0,1,'0/1','shiro:add','button'),(3,'/shiro/update','用户更新',0,1,'0/1','shiro:update','button'),(4,'/shiro/userInfo','用户信息',0,0,'0','shiro:view','button');
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(32) DEFAULT NULL COMMENT '角色名称',
`type` varchar(10) DEFAULT NULL COMMENT '角色类型',
`status` int(11) DEFAULT '0' COMMENT '0可用,-1不可用',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
insert into `role`(`id`,`name`,`type`,`status`) values (1,'admin','管理员',0),(2,'vip','VIP会员',0);
DROP TABLE IF EXISTS `role_permission`;
CREATE TABLE `role_permission` (
`rid` bigint(20) DEFAULT NULL COMMENT '角色ID',
`pid` bigint(20) DEFAULT NULL COMMENT '权限ID'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into `role_permission`(`rid`,`pid`) values (1,3),(1,1),(1,2),(2,4),(1,4);
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL COMMENT '用户昵称',
`email` varchar(128) DEFAULT NULL COMMENT '邮箱|登录帐号',
`password` varchar(32) DEFAULT NULL COMMENT '密码',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间',
`status` int(1) DEFAULT '1' COMMENT '1:有效,0:禁止登录',
`salt` varchar(100) DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
insert into `user`(`id`,`name`,`email`,`password`,`create_time`,`last_login_time`,`status`,`salt`,`update_time`) values (1,'管理员',NULL,'5BB8026FD6A8DC246B2E3F9D42D5EB4B',NULL,'2017-12-09 13:46:37',1,'d3Frh4BgfT','2017-12-09 13:46:37'),(2,'',NULL,NULL,NULL,NULL,1,NULL,NULL);
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`uid` bigint(20) DEFAULT NULL COMMENT '用户ID',
`rid` bigint(20) DEFAULT NULL COMMENT '角色ID'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into `user_role`(`uid`,`rid`) values (1,1);
4,shiro的配置
主配置文件ShiroConfig
package com.shiro.demo.config;
import com.shiro.demo.domain.Permission;
import com.shiro.demo.service.UserRolePermissionService;
import com.shiro.demo.shiro.MyShiroRealm;
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.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @author tracyclock 2017-12-08
* shiro过滤url的配置
*/
@ConfigurationProperties(prefix = "shiroFilter")
@Configuration
public class ShiroConfig{
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private UserRolePermissionService userRolePermissionService;
//在application.yml配置文件中配置无需登录验证的url
private Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>();
public Map<String, String> getFilterChainDefinitionMap() {
return filterChainDefinitionMap;
}
public void setFilterChainDefinitionMap(Map<String, String> filterChainDefinitionMap) {
this.filterChainDefinitionMap = filterChainDefinitionMap;
}
@Bean
public ShiroFilterFactoryBean shiroFilter(org.apache.shiro.mgt.SecurityManager securityManager) {
logger.info("ShiroConfig shiroFilter() start~");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//如果不设置默认会自动寻找Web工程根目录下的"/login"页面
shiroFilterFactoryBean.setLoginUrl("/shiro/login");
//登录成功后要跳转的链接,备用
shiroFilterFactoryBean.setSuccessUrl("/shiro/index");
//未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
//filterChainDefinitionMap继续添加需要权限验证的url,可以在类中配置,也能在yml文件配置,这里采用数据库的配置
//例子:filterChainDefinitionMap.put("/add", "perms[权限添加]");
//获得数据库需要权限验证的所有信息
String permissionStr = "perms[%s]";
List<Permission> permissionList = this.userRolePermissionService.permissionList();
logger.info("ShiroConfig shiroFilter() permissionList{}",permissionList);
if(permissionList != null && permissionList.size() > 0){
for (Permission permission : permissionList) {
filterChainDefinitionMap.put(permission.getUrl(), String.format(permissionStr,permission.getName()));
}
}
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public MyShiroRealm myShiroRealm(){
MyShiroRealm myShiroRealm = new MyShiroRealm();
return myShiroRealm;
}
@Bean
public org.apache.shiro.mgt.SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
return securityManager;
}
}
需自己实现业务逻辑的MyShiroReaml
package com.shiro.demo.shiro;
import com.shiro.demo.domain.Permission;
import com.shiro.demo.domain.Role;
import com.shiro.demo.domain.User;
import com.shiro.demo.service.UserRolePermissionService;
import com.shiro.demo.service.UserService;
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.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.PostConstruct;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* @author tracyclock 2017-12-08
* 用于进行权限信息的验证,需要我们自己实现
*/
public class MyShiroRealm extends AuthorizingRealm {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private UserService userService;
@Autowired
private UserRolePermissionService userRolePermissionService;
/**
* AuthorizationInfo 是授权访问控制,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等。
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
logger.info("用户授权-->MyShiroRealm doGetAuthorizationInfo() start~");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
User user = (User)principalCollection.getPrimaryPrincipal();
//用户role集合
List<Role> roleList = userRolePermissionService.roleListByUserId(user.getId());
if(roleList == null || roleList.size() <= 0){
return authorizationInfo;
}
Set<Long> roleIds = new HashSet<>();
Set<String> roleSet = new HashSet<>();
Set<String> permissionSet = new HashSet<>();
for(Role role : roleList){
roleSet.add(role.getType());
roleIds.add(role.getId());
}
logger.info("MyShiroRealm doGetAuthorizationInfo() roleSet{}",roleSet);
//用户permission集合
List<Permission> permissionList = this.userRolePermissionService.permissionListByRoleIds(roleIds);
if(permissionList != null && permissionList.size() > 0){
for (Permission permission :permissionList) {
permissionSet.add(permission.getName());
}
}
logger.info("MyShiroRealm doGetAuthorizationInfo() permissionSet{}",permissionSet);
//将用户的角色信息,权限信息,添加到authorizationInfo
//debug可以看到,返回authorizationInfo后,就开始验证权限是否符合,未授权就会跳转之前配置的未授权页面
authorizationInfo.setRoles(roleSet);
authorizationInfo.setStringPermissions(permissionSet);
return authorizationInfo;
}
/**
* AuthenticationInfo 是用来验证用户身份,判断登陆是否成功
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
logger.info("用户验证----->MyShiroRealm doGetAuthenticationInfo() start~");
//从token获取用户的输入的账号
UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
String name = token.getUsername();//得到用戶名
//通过name从数据库中查找 User对象
User user = userService.getUserByName(name);
//此处可用缓存机制实现N点登陆,几次登陆失败后禁止登陆等操作
if(user == null){
throw new AccountException("帐号不正确!");
}else if(user.getStatus().intValue() == 0){
throw new DisabledAccountException("此帐号已经设置为禁止登录!");
}
logger.info("doGetAuthenticationInfo() user{}",user);
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user,user.getPassword(), ByteSource.Util.bytes(user.getSalt()),getName());
return authenticationInfo;
}
/**
* 重写shiro的密码验证方式
* CustomCredentialsMatcher 自己实现的加盐验证
*/
@PostConstruct
public void initCredentialsMatcher() {
setCredentialsMatcher(new CustomCredentialsMatcher());
}
}
根据自己业务重写的密码加盐后验证
package com.shiro.demo.shiro;
import com.shiro.demo.utils.MD5Util;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
/**
* @author tracyclock 2017-12-08
* shiro自定义密码验证
*/
public class CustomCredentialsMatcher extends SimpleCredentialsMatcher {
@Override
public boolean doCredentialsMatch(AuthenticationToken authcToken, AuthenticationInfo info) {
//传入的参数进行类型转换
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
SimpleAuthenticationInfo simpleInfo = (SimpleAuthenticationInfo)info;
//获取传入的盐
String salt = new String(simpleInfo.getCredentialsSalt().getBytes());
//tokenCredentials,用户输入的密码经过加密后的密码 accountCredentials:传入的数据库密码
Object tokenCredentials = encrypt(String.valueOf(token.getPassword()),salt);
Object accountCredentials = getCredentials(info);
//将密码加密与系统加密后的密码校验,内容一致就返回true,不一致就返回false
return equals(tokenCredentials, accountCredentials);
}
//自定义加密方法,将传进来密码用MD5加盐加密
private String encrypt(String password,String salt) {
return MD5Util.getMD5CodeBySalt(password,salt);
}
}
MD5加盐参考 http://blog.csdn.net/tracyclock/article/details/73484401
5.写controller进行验证
@Controller
@RequestMapping("/shiro")
public class ShiroController {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private UserService userService;
@RequestMapping("/index")
public String index(){
logger.info("go index");
return "index";
}
@RequestMapping("/login")
public String login(){
logger.info("go login");
return "login";
}
@RequestMapping("/add")
public String add(){
logger.info("go add");
return "add";
}
@RequestMapping("/update")
public String update(){
logger.info("go update");
return "update";
}
@RequestMapping(value = "/loginAction",method = RequestMethod.POST)
public String loginAction(@RequestParam("name") String name, @RequestParam("password") String password){
logger.info("go loginAction");
Map<String,String> map = new HashMap<>();
UsernamePasswordToken token = new UsernamePasswordToken(name,password.toCharArray());
try {
//将用户的登陆交给shiro去验证,登陆失败就重定向到登录页
SecurityUtils.getSubject().login(token);
} catch (AuthenticationException e) {
logger.error("login failed,exception is {}",e.getMessage());
return "redirect:/shiro/login";
}
//验证通过后,更新最近登陆时间
User user = (User)SecurityUtils.getSubject().getPrincipal();
user.setLastLoginTime(new Date());
userService.updateUser(user);
return "redirect:/shiro/userInfo";
}
@RequestMapping(value = "/userInfo")
public String userInfo(ModelMap modelMap){
//通过验证后,可以直接取到user对象
User user = (User)SecurityUtils.getSubject().getPrincipal();
modelMap.put("user",user);
return "userInfo";
}
/**
* shiro直接退出登陆
*/
@RequestMapping(value="logout")
public String logout(){
try {
SecurityUtils.getSubject().logout();
} catch (Exception e) {
logger.error(e.getMessage());
}
return "redirect:/shiro/index";
}
}
附一张目录结构
项目git地址:https://gitee.com/jiaobai/shiro_demo