spring boot 整合mybatis 和 shiro 实现登录

目录

 

Spring boot 后台代码

项目目录结构

基本环境配置

修改pom文件

mybatis整合

添加jdbc.properties文件

数据源配置

添加mybatis-config.xml文件

mybatis配置

创建user对象测试

model

dao

service

访问测试

shiro整合

ShiroFilter工厂

测试登录


Spring boot 后台代码

项目目录结构


基本环境配置

修改pom文件

添加所需的jar

<!-- myBatis -->
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.3.2</version>
		</dependency>

		<!-- druid数据库连接池 -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.0.26</version>
		</dependency>

		<!-- log4j -->
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>1.2.17</version>
		</dependency>

		<!-- mysql -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>

		<!-- 分页插件 -->
		<dependency>
			<groupId>com.github.pagehelper</groupId>
			<artifactId>pagehelper</artifactId>
			<version>4.1.6</version>
		</dependency>

		<!-- fastjson -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.4</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>

		<!-- org.apache.commons -->
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
		</dependency>
		<dependency>
			<groupId>commons-io</groupId>
			<artifactId>commons-io</artifactId>
			<version>2.4</version>
		</dependency>
		<dependency>
			<groupId>commons-fileupload</groupId>
			<artifactId>commons-fileupload</artifactId>
			<version>1.3.1</version>
		</dependency>

mybatis整合

添加jdbc.properties文件

在resources目录先创建jdbc.properties

#数据库配置
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/springbootdemo?useUnicode=true&characterEncoding=utf8&useSSL=false
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# 连接池配置
# 初始化大小,最小,最大
spring.datasource.initialSize=5  
spring.datasource.minIdle=5  
spring.datasource.maxActive=20  
# 配置获取连接等待超时的时间
spring.datasource.maxWait=60000  
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.timeBetweenEvictionRunsMillis=60000  
# 配置一个连接在池中最小生存的时间,单位是毫秒
spring.datasource.minEvictableIdleTimeMillis=300000
# 测试连接是否有效的sql
spring.datasource.validationQuery=select 'x'
# 建议配置为true,不影响性能,并且保证安全性
# 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效
spring.datasource.testWhileIdle=true
# 申请连接时执行validationQuery检测连接是否有效
spring.datasource.testOnBorrow=false
# 归还连接时执行validationQuery检测连接是否有效
spring.datasource.testOnReturn=false
# 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
# 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:
# 监控统计用的filter:stat
# 日志用的filter:log4j
# 防御sql注入的filter:wall
spring.datasource.filters=stat,log4j,wall

数据源配置

在DataSource包下创建DataSourceConfig.java

/**
 * 数据源
 */
@Configuration
@MapperScan("cn.ac.sec.dao")
public class DataSourceConfig {
    private static final Logger logger = LoggerFactory.getLogger(DataSourceConfig.class);

    @Autowired
    private JdbcConfig jdbcConfig;

    @Bean
    @Primary //在同样的DataSource中,首先使用被标注的DataSource
    public DataSource dataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl(jdbcConfig.getUrl());
        druidDataSource.setUsername(jdbcConfig.getUserName());
        druidDataSource.setPassword(jdbcConfig.getPassword());
        druidDataSource.setInitialSize(jdbcConfig.getInitialSize());
        druidDataSource.setMinIdle(jdbcConfig.getMinIdle());
        druidDataSource.setMaxActive(jdbcConfig.getMaxActive());
        druidDataSource.setTimeBetweenEvictionRunsMillis(jdbcConfig.getTimeBetweenEvictionRunsMillis());
        druidDataSource.setMinEvictableIdleTimeMillis(jdbcConfig.getMinEvictableIdleTimeMillis());
        druidDataSource.setValidationQuery(jdbcConfig.getValidationQuery());
        druidDataSource.setTestWhileIdle(jdbcConfig.isTestWhileIdle());
        druidDataSource.setTestOnBorrow(jdbcConfig.isTestOnBorrow());
        druidDataSource.setTestOnReturn(jdbcConfig.isTestOnReturn());
        druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(jdbcConfig.getMaxPoolPreparedStatementPerConnectionSize());
        try {
            druidDataSource.setFilters(jdbcConfig.getFilters());
        } catch (SQLException e) {
            if (logger.isInfoEnabled()) {
                logger.info(e.getMessage(), e);
            }
        }
        return druidDataSource;
    }


    /**
     * Jdbc配置类
     */
    @PropertySource(value = "classpath:jdbc.properties")
    @Component
    @Data
    public static class JdbcConfig {
        /**
         * 数据库用户名
         */
        @Value("${spring.datasource.username}")
        private String userName;
        /**
         * 驱动名称
         */
        @Value("${spring.datasource.driver-class-name}")
        private String driverClass;
        /**
         * 数据库连接url
         */
        @Value("${spring.datasource.url}")
        private String url;
        /**
         * 数据库密码
         */
        @Value("${spring.datasource.password}")
        private String password;

        /**
         * 数据库连接池初始化大小
         */
        @Value("${spring.datasource.initialSize}")
        private int initialSize;

        /**
         * 数据库连接池最小最小连接数
         */
        @Value("${spring.datasource.minIdle}")
        private int minIdle;

        /**
         * 数据库连接池最大连接数
         */
        @Value("${spring.datasource.maxActive}")
        private int maxActive;

        /**
         * 获取连接等待超时的时间
         */
        @Value("${spring.datasource.maxWait}")
        private long maxWait;

        /**
         * 多久检测一次
         */
        @Value("${spring.datasource.timeBetweenEvictionRunsMillis}")
        private long timeBetweenEvictionRunsMillis;

        /**
         * 连接在池中最小生存的时间
         */
        @Value("${spring.datasource.minEvictableIdleTimeMillis}")
        private long minEvictableIdleTimeMillis;

        /**
         * 测试连接是否有效的sql
         */
        @Value("${spring.datasource.validationQuery}")
        private String validationQuery;

        /**
         * 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,检测连接是否有效
         */
        @Value("${spring.datasource.testWhileIdle}")
        private boolean testWhileIdle;

        /**
         * 申请连接时,检测连接是否有效
         */
        @Value("${spring.datasource.testOnBorrow}")
        private boolean testOnBorrow;

        /**
         * 归还连接时,检测连接是否有效
         */
        @Value("${spring.datasource.testOnReturn}")
        private boolean testOnReturn;

        /**
         * PSCache大小
         */
        @Value("${spring.datasource.maxPoolPreparedStatementPerConnectionSize}")
        private int maxPoolPreparedStatementPerConnectionSize;

        /**
         * 通过别名的方式配置扩展插件
         */
        @Value("${spring.datasource.filters}")
        private String filters;
    }
}

添加mybatis-config.xml文件

在resources目录下创建mybatis-config.xml

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>    
       <plugins>
	    <!-- com.github.pagehelper为PageHelper类所在包名 -->
	    <plugin interceptor="com.github.pagehelper.PageHelper">
	        <!-- 4.0.0以后版本可以不设置该参数 -->
	        <property name="dialect" value="mysql"/>
	        <!-- 该参数默认为false -->
	        <!-- 设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用 -->
	        <!-- 和startPage中的pageNum效果一样-->
	        <property name="offsetAsPageNum" value="false"/>
	        <!-- 该参数默认为false -->
	        <!-- 设置为true时,使用RowBounds分页会进行count查询 -->
	        <property name="rowBoundsWithCount" value="true"/>
	        <!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 -->
	        <!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型)-->
	        <property name="pageSizeZero" value="true"/>
	        <!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 -->
	        <!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 -->
	        <!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 -->
	        <property name="reasonable" value="false"/>
	        <!-- 3.5.0版本可用 - 为了支持startPage(Object params)方法 -->
	        <!-- 增加了一个`params`参数来配置参数映射,用于从Map或ServletRequest中取值 -->
	        <!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,orderBy,不配置映射的用默认值 -->
	        <!-- 不理解该含义的前提下,不要随便复制该配置 -->
	        <property name="params" value="pageNum=pageHelperStart;pageSize=pageHelperRows;"/>
	        <!-- 支持通过Mapper接口参数来传递分页参数 -->
	        <property name="supportMethodsArguments" value="false"/>
	        <!-- always总是返回PageInfo类型,check检查返回类型是否为PageInfo,none返回Page -->
	        <property name="returnPageInfo" value="none"/>
	    </plugin>
	</plugins>
	
	<mappers>  
		<mapper resource="mapper/UserDAO.xml"/>
    </mappers>
</configuration>

mybatis配置

在dataSource下创建SessionFactoryConfig.java

@Configuration
@EnableTransactionManagement // 启注解事务管理,等同于xml配置方式的 <tx:annotation-driven />
public class SessionFactoryConfig {

    /**
     * mybatis 配置路径
     */
    private static String MYBATIS_CONFIG = "mybatis-config.xml";

    @Autowired
    private DataSource dataSource;


    /***
     *  创建sqlSessionFactoryBean
     *  并且设置configtion 如驼峰命名.等等
     *  设置mapper 映射路径
     *  设置datasource数据源
     *
     * @Title: createSqlSessionFactoryBean
     * @author: hongyangliao
     * @Date: 18-1-3 上午9:52
     * @param
     * @return org.mybatis.spring.SqlSessionFactoryBean sqlSessionFactoryBean实例
     * @throws
     */
    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactoryBean createSqlSessionFactoryBean() throws IOException {
        SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
        // 设置mybatis configuration 扫描路径
        sqlSessionFactory.setConfigLocation(new ClassPathResource(MYBATIS_CONFIG));
        // 设置datasource
        sqlSessionFactory.setDataSource(dataSource);
        return sqlSessionFactory;
    }
}

在resources目录下创建mapper文件夹,内部创建对应mapper.xml


创建user对象测试

model

新建model包,添加User.java

@Data
public class User {

	private String id;
	
	private String userName;
	
	private String password;
	
}

dao

新建dao包,添加UserDAO.java

public interface UserDAO {

	/*
	 * 根据用户名获取用户对象,用于登录校验
	 */
	User selectUserByUserName(String userName);
	
}

service

新建service包,添加UserService.java

/**
 * 
* @ClassName: UserService
* @Description: userService接口
* @author yett
* @date Mar 22, 2019 1:35:44 PM
 */
public interface UserService {

	/*
	 * 根据用户名获取用户对象,用于登录校验
	 */
	User getUserByUserName(String userName); 
	
}

创建service接口实现类,UserServiceImpl.java

@Service
public class UserServiceImpl implements UserService{

	@Autowired
	private UserDAO userDAO;
	
	@Override
	public User getUserByUserName(String userName) {
		User user = userDAO.selectUserByUserName(userName);
		return user;
	}

}

在mapper目录下创建UserDAO.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="cn.ac.sec.dao.UserDAO" >
    <resultMap id="BaseResultMap" type="cn.ac.sec.model.User" >
        <id column="id" property="id"/>
        <result column="user_name" property="userName"/>
        <result column="password" property="password"/>
    </resultMap>
    <sql id="Base_Column_List" >
        id, user_name, password
    </sql>
    <select id="selectUserByUserName" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from user
        where user_name = #{userName}
    </select>
  
</mapper>

创建测试controller 判断是否配置成功

@RestController
public class TestController {
	
	@Autowired
	private UserService userService;
	
//	@GetMapping("/hello")
//	public String index() {
//		return "hello world!!!";
//	}
	
	@GetMapping("/getUserByUserName")
	public User getUserByUserName(String userName) {
		User user = userService.getUserByUserName(userName);
		return user;
	}
}

访问测试

浏览器访问http://localhost:8081/getUserByUserName?userName=admin,返回user对象


shiro整合

ShiroFilter工厂

自定义MySessionManager类继承DefaultWebSessionManager类,重写getSessionId方法
 

/**
 * 
* @ClassName: MySessionManager
* @Description: 自定义sessionId获取
* @author yett
* @date Mar 19, 2019 3:37:26 PM
 */
public class MySessionManager extends DefaultWebSessionManager {
 
    private static final String AUTHORIZATION = "Authorization";
 
    private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
 
    public MySessionManager() {
        super();
    }
 
    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
        //如果请求头中有 Authorization 则其值为sessionId
        if (!StringUtils.isEmpty(id)) {
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            return id;
        } else {
            //否则按默认规则从cookie取sessionId
            return super.getSessionId(request, response);
        }
    }

}

创建shiroConfig配置类

/**
 * 
* @ClassName: ShiroConfig
* @Description: Shiro配置类
* @author yett
* @date Mar 12, 2019 2:07:30 PM
 */
@Configuration
public class ShiroConfig {

	/**
     * ShiroFilterFactoryBean 处理拦截资源文件问题。
     * 注意:单独一个ShiroFilterFactoryBean配置是或报错的,以为在
     * 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
     *
     * Filter Chain定义说明 1、一个URL可以配置多个Filter,使用逗号分隔 2、当设置多个过滤器时,全部验证通过,才视为通过
     * 3、部分过滤器可指定参数,如perms,roles
     *
     */
	@Bean
	public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
		ShiroFilterFactoryBean shiroFilterFactoryBean  = new ShiroFilterFactoryBean();
		
		//设置SecurityManager
		shiroFilterFactoryBean.setSecurityManager(securityManager);
		
		// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        shiroFilterFactoryBean.setLoginUrl("/login.html");

        // 拦截器.
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        // 配置不会被拦截的链接 顺序判断
        filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/login", "anon");

        // 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
        filterChainDefinitionMap.put("/logout", "logout");

        // 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 :这是一个坑呢,一不小心代码就不好使了;
        // ① authc:所有url都必须认证通过才可以访问; ② anon:所有url都都可以匿名访问
        filterChainDefinitionMap.put("/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
	}
	
	@Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置realm.
        securityManager.setRealm(myRealm());
       // 自定义session管理
        securityManager.setSessionManager(sessionManager());  
        return securityManager;
    }
	
	//自定义sessionManager  
    @Bean  
    public SessionManager sessionManager() {  
        MySessionManager mySessionManager = new MySessionManager();  
        return mySessionManager;  
    }  

	/**
     * 身份认证realm; (这个需要自己写,账号密码校验;权限等)
     * 
     * @return
     */
    @Bean
    public MyRealm myRealm() {
        MyRealm myRealm = new MyRealm();
        return myRealm;
    }

    /**
     * Shiro生命周期处理器
     * @return
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
     * 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
     * @return
     */
    @Bean
    @DependsOn({ "lifecycleBeanPostProcessor" })
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
        return authorizationAttributeSourceAdvisor;
    }

}

附上自定realm

MyRealm.java

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;

import cn.ac.sec.model.User;
import cn.ac.sec.service.UserService;

public class MyRealm extends AuthorizingRealm {

	@Autowired
	private UserService userService;
	
	/*
	 * 授权
	 * @see org.apache.shiro.realm.AuthorizingRealm#doGetAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection)
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		// TODO Auto-generated method stub
		return null;
	}

	/*
	 * 认证
	 * @see org.apache.shiro.realm.AuthenticatingRealm#doGetAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken)
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		String userName = token.getPrincipal().toString();
        User user = userService.getUserByUserName(userName);
        if (user != null) {
            AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(user, user.getPassword(), getName());
            return authcInfo;
        } else {
            return null;
        }
	}
}

测试登录

创建LoginController.java

@RestController
public class LoginController {

	@RequestMapping("/login")
    public String login(HttpServletRequest request) {
		String loginName = request.getParameter("userName");
		String password = request.getParameter("password");
		System.err.println(loginName+"=="+password);
		Subject subject = SecurityUtils.getSubject();
		UsernamePasswordToken token = new UsernamePasswordToken(loginName,password);
		if (StringUtils.isNotEmpty(loginName) && StringUtils.isNotEmpty(password)) {
			try{
				subject.login(token);	
				User user = (User)subject.getPrincipal();	
				subject.getSession().setAttribute("DT_LOGIN_USER", user);
			}catch(Exception ex){
				return "shiro 登录测试失败";
			}
		}
		return "shiro 登录测试成功";
    }
	
}

login.html

<!DOCTYPE html>
<html>

    <head>
        <meta charset="UTF-8">
        <title>Insert title here</title>
    </head>

    <body>
        <h1>登录页</h1>
        <form action="/user/login" method="post">
            <!-- 1、参数必须被命名为 userName,不能为 username,保持和实体中的属性一致 -->
            用户名:<input type="text" id="userName" name="userName" placeholder="userName"></input><br/>
            <!-- 2、同理密码参数必须被命名为 password -->
            密码:<input type="password" id="password" name="password" placeholder="password"></input><br/>
            <input type="submit" value="登录"></input>
        </form>
    </body>

</html>

浏览器访问http://localhost:8081/login.html 

成功返回

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值