从零开始搭建springboot项目4:shiro框架+thymeleaf配置

 

shiro和spring security 都是安全框架,都可以授权认证。

对比来讲spring security自定义能力更强点,shiro单纯配置来说更复杂点.

不过我技术不精,还是使用我更熟悉的shiro来进行授权认证功能的实现

下图是shiro的3层构造,而我们写也是根据这三层构造来写的,用户->安全事务管理器->realm对象

 

1.依赖shiro包

还是在pom文件写入

		<!--shiro整合spring-->
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring-boot-starter</artifactId>
			<version>1.5.1</version>
		</dependency>

 

2.创建config文件和Realm文件

config文件:

@Configuration
public class ShiroConfig {
    //shiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
        //关联SecurityManager
        bean.setSecurityManager(securityManager);
        return bean;
    }

    //DefaultwebSecurityManager 配置核心安全事务管理器
    @Bean(name="securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("myRealm") MyRealm myRealm){
        DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
        //关联Realm
        securityManager.setRealm(myRealm);
        return securityManager;
    }

    //创建realm对象(需要自定义)
    @Bean
    public MyRealm myRealm(@Qualifier("credentialsMatcher") HashedCredentialsMatcher credentialsMatcher){
        MyRealm myRealm=new MyRealm();
        myRealm.setCredentialsMatcher(credentialsMatcher);
        return myRealm;
    }

    //以上三层是shiro的三层结构
    //下面是附加的

    // 配置密码比较器(密码加密)
    @Bean(name="credentialsMatcher")
    public HashedCredentialsMatcher credentialsMatcher() {
        //RetryLimitHashedCredentialsMatcher为另外类的构造函数
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        credentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
        credentialsMatcher.setHashIterations(0);//散列的次数,比如散列两次,相当于 md5(md5(""));
        return credentialsMatcher;
    }
}

Realm文件:

public class MyRealm extends AuthorizingRealm {
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行授权!!!!!");
        return null;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行认证!!!!!");
        return null;
    }
}

 

3.完善config文件

config文件:

我们还可以在shiroFilterFactoryBean那一层加配置设置很多东西

    //shiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
        //关联SecurityManager 配置核心安全事务管理器
        bean.setSecurityManager(securityManager);
        //配置登录的URL(未登录的用户访问的页面)
        //bean.setLoginUrl("/auth/login");
        // 配置登录成功的url(登录后用户访问的页面)
        //bean.setSuccessUrl("/auth/index");

        //自定义拦截器
        Map<String,Filter> MyfiltersMap=new LinkedHashMap<String,Filter>();
        //限制同一个账号的同时在线个数
        //MyfiltersMap.put("kickout",kickout)
        //配置访问权限
        //<url,权限类型>
        Map<String,String> filterChainDefinitionMap=new LinkedHashMap<>();
        //注意过滤器配置顺序 不能颠倒
        //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了,登出后跳转配置的loginUrl
        filterChainDefinitionMap.put("/auth/logout","logout");
        // 配置不会被拦截的链接 顺序判断
        //authc:所有url都必须认证通过才可以访问;
        // anon:所有url都都可以匿名访问
        filterChainDefinitionMap.put("/css/**","anon");
        filterChainDefinitionMap.put("/js/**","anon");
        filterChainDefinitionMap.put("/img/**","authc");//表示需要认证才可以访问
        filterChainDefinitionMap.put("/auth/login","anon");
        filterChainDefinitionMap.put("/*", "anon");
        filterChainDefinitionMap.put("/**", "anon");
       //设置授权形式的访问
        filterChainDefinitionMap.put("/sardine/**","perms[admin:play]");

        //设置未授权界面
        bean.setUnauthorizedUrl("/403");
        //配置shiro的主要拦截器(默认有个拦截器)
        bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return bean;
    }

anon: 无需认证即可访问
authc: 需要认证才可访问
user: 点击“记住我”功能可访问
perms: 拥有权限才可以访问
role: 拥有某个角色权限才能访问

 

4.完善Realm文件之登录验证

前一章偷懒的,这里补上,创建业务层,并且多加个login方法进行登录验证

public interface UsersService {
    List<Users> queryUserList();
    Users queryUserByUserName(String username);
    Users queryUserById(int id);
    ResponseBean login(String username,String password);
}
@Service
public class UsersServiceImpl implements UsersService{
    @Autowired
    private UsersMapper usersMapper;

    @Override
    public List<Users> queryUserList() {
        return usersMapper.queryUserList();
    }

    @Override
    public Users queryUserByUserName(String username) {
        return usersMapper.queryUserByUserName(username);
    }

    @Override
    public Users queryUserById(int id) {
        return usersMapper.queryUserById(id);
    }

    @Override
    public ResponseBean login(String username, String password) {
        //获取当前用户
        Subject subject= SecurityUtils.getSubject();
        //设置当前用户的登录数据
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        //登录认证  注意这里的帐号密码是跟Realm文件那边设置的进行对比验证
        try{
            subject.login(token);
        } catch (IncorrectCredentialsException ice) {
            // 捕获密码错误异常
            return new ResponseBean(ResponseCode.Fail.GetValue(),"密码错误",null);
        } catch (UnknownAccountException uae) {
            // 捕获未知用户名异常
            return new ResponseBean(ResponseCode.Fail.GetValue(),"账号异常",null);
        } catch (ExcessiveAttemptsException eae) {
            // 捕获错误登录过多的异常
            return new ResponseBean(ResponseCode.Fail.GetValue(),"错误次数过多,账号被锁定,请于10分钟后再进行尝试",null);
        }catch (LockedAccountException lae){
            return new ResponseBean(ResponseCode.Fail.GetValue(),"帐号未激活",null);
        }catch (AuthenticationException ae){
            return new ResponseBean(ResponseCode.Fail.GetValue(),"帐号不存在",null);
        }
        ResponseBean responseBean = null;
        Users user = queryUserByUserName(username);
        subject.getSession().setAttribute("user", user);
        SecurityUtils.getSubject().getSession().setTimeout(-1000l);
        return new ResponseBean(ResponseCode.Success.GetValue(),"登陆成功",user);
    }
}

 

然后在Realm文件中写方法

public class MyRealm extends AuthorizingRealm {
    @Autowired
    private UsersService usersService;

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行授权!!!!!");
        return null;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行认证!!!!!");
        //这里就获取登录时当前用户设置的登录信息
        UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
        //根据当前用户的用户名查找数据库的密码
        Users users=usersService.queryUserByUserName(token.getUsername());
        if(users!=null){
            //根据用户名在数据库找不到,重返login页面
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.setViewName("redirect:/login");
            modelAndView.addObject("msg","账号不存在");
            //return null;
            throw new AuthenticationException("帐号不存在");
        }
        else{
            //根据用户名在数据库找到用户了,那么获取密码,并返回,进行比较
            return new SimpleAuthenticationInfo(users.getUsername(),users.getPassword(),this.getClass().getName());
        }
    }
}

至此,shiro的登录认证功能基本已配置完。

 

Realm文件登录验证测试(导入thymeleaf):

到实际测试阶段,这时,我选择偷懒,所以先导入themleaf依赖,还是pom文件

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>

然后继续yml文件配置

spring下面加入

  #thymeleaf设置
  #模板编码
  thymeleaf:
    encoding: UTF-8
    cache: false        #启用模板缓存(开发时建议关闭)
    mode: HTML5          #应用于模板的模板模式ervlet
    servlet:
      content-type: text/html   #Content-Type值
    enabled: true        #启用MVC Thymeleaf视图分辨率
    prefix: classpath:/templates/    #在构建URL时预先查看名称的前缀
    suffix: .html            #构建URL时附加查看名称的后缀

整体:

spring:
  #热部署
  devtools:
    restart:
      enabled: true
      additional-paths: src/main/java
  #数据库配置
  datasource:
    #1.JDBC
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://xx.xxx.xxx.xxx/sardines?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
    username: root
    password: root
    druid:
      #2.连接池配置
      #初始化连接池的连接数量 大小,最小,最大
      initial-size: 5
      min-idle: 5
      max-active: 20
      #配置获取连接等待超时的时间
      max-wait: 60000
      #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      time-between-eviction-runs-millis: 60000
      # 配置一个连接在池中最小生存的时间,单位是毫秒
      min-evictable-idle-time-millis: 30000
      validation-query: SELECT 1 FROM DUAL
      test-while-idle: true
      test-on-borrow: true
      test-on-return: false
      # 是否缓存preparedStatement,也就是PSCache  官方建议MySQL下建议关闭   个人建议如果想用SQL防火墙 建议打开
      pool-prepared-statements: true
      max-pool-prepared-statement-per-connection-size: 20
      # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
      filter:
        stat:
          merge-sql: true
          slow-sql-millis: 5000
      #3.基础监控配置
      web-stat-filter:
        enabled: true
        url-pattern: /*
        #设置不统计哪些URL
        exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"
        session-stat-enable: true
        session-stat-max-count: 100
      stat-view-servlet:
        enabled: true
        url-pattern: /druid/*
        reset-enable: true
        #设置监控页面的登录名和密码
        login-username: admin
        login-password: 123456
        allow: 127.0.0.1
        #deny: 122.1.1.1
  #thymeleaf设置
  #模板编码
  thymeleaf:
    encoding: UTF-8
    cache: false        #启用模板缓存(开发时建议关闭)
    mode: HTML5          #应用于模板的模板模式ervlet
    servlet:
      content-type: text/html   #Content-Type值
    enabled: true        #启用MVC Thymeleaf视图分辨率
    prefix: classpath:/templates/    #在构建URL时预先查看名称的前缀
    suffix: .html            #构建URL时附加查看名称的后缀


#mybatis配置
mybatis:
  #设置基本包 # 注意:对应实体类的路径
  type-aliases-package: com.sardine.myproject.pojo
  #告诉去哪找xml文件 #注意:一定要对应mapper映射xml文件的所在路径
  mapper-locations: classpath:mybatis/mapper/*.xml
server:
  port: 8080

 

至此,shiro框架的用户认证和thymeleaf配置就完成了。

实际使用

接下来,就是在前端页面引用thymeleaf,然后后端写代码进行操作了,我暂时做了个简单的登录,下面是具体代码。

ResponseCode文件和UserController文件:

public enum ResponseCode {
    Success(200),
    Fail(400),
    Error(404);

    private int value = 0;
    private ResponseCode(int value) {     //必须是private的,否则编译错误
        this.value = value;
    }
    public int GetValue(){
        return  this.value;
    }

}
@Controller
public class UserController {
    @Autowired
    private UsersMapper usersMapper;
    @Autowired
    private UsersService usersService;

    @GetMapping({"/"})
    public String toLogin(){
         return "login";
    }

    @PostMapping("/login")
    public String login(String username,String password){
        System.out.println("username:"+username+"  password:"+password);
        ResponseBean responseBean= usersService.login(username,password);
        if(responseBean.getCode()== ResponseCode.Success.GetValue()){
            return "index";
        }
        else{
            System.out.println("登录失败:"+responseBean.getMsg());
            //System.out.println("登录失败");
            return "login";
        }
    }
}

UserMapper.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="com.sardine.mapper.UsersMapper">
    <resultMap id="userResultMap" type="com.sardine.pojo.Users">
        <id column="id" jdbcType="BIGINT" property="id"/>
        <result column="username" jdbcType="VARCHAR" property="username"/>
        <result column="password" jdbcType="VARCHAR" property="password"/>
        <result column="status" jdbcType="INTEGER" property="status"/>
    </resultMap>
    <sql id="BASE_TABLE">
        users a
    </sql>
    <sql id="BASE_COLUMN">
        a.*
    </sql>
    <select id="queryUserList" resultMap="userResultMap">
        SELECT
        <include refid="BASE_COLUMN"/>
        FROM
        <include refid="BASE_TABLE"/>
    </select>
    <select id="queryUserByUserName" resultMap="userResultMap" parameterType="String">
        SELECT
        <include refid="BASE_COLUMN"/>
        FROM
        <include refid="BASE_TABLE"/>
        <where>
            a.username = #{username}
        </where>
    </select>
    <select id="queryUserById" resultMap="userResultMap" parameterType="int">
        SELECT
        <include refid="BASE_COLUMN"/>
        FROM
        <include refid="BASE_TABLE"/>
        <where>
            id = #{id}
        </where>
    </select>

</mapper>

前端文件:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>测试</title>
</head>
<body>
    <h1>登录</h1>
    <form method="post" th:action="@{/login}">
        <p>用户名: <input type="text" name="username"></p>
        <p>密码: <input type="text" name="password"></p>
        <p><input type="submit"></p>
    </form>
</body>
</html>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
   <h1>登录成功</h1>
</body>
</html>

PS:这里讲几个点:

1.themleaf要用<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">这句话必不可少

2.themleaf语法请自学

3.为了统一规格我额外建了个enum类,嫌麻烦直接写数字也可以

4.ReaponseBean是比较烂大街的自己写的,返回code、msg、data这三个数据的一个类,这里就不贴出来了。

5.由于我写的是加密的密码比较器,所以数据库那边的密码不是123456,而是md5加密后的数据

密码:123456 转换成这里写的md5加密后的结果是:e10adc3949ba59abbe56e057f20f883e

 

 

5.用户认证做完,继续完善Realm文件的权限认证

修改Realm文件,这里也就不写实际应用了,无非就是能进去,不能进去,然后在config文件已经设置过授权页面和未授权页面的跳转了。

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行授权!!!!!");
        SimpleAuthorizationInfo Info = new SimpleAuthorizationInfo();
        //获取登录后的用户,如果用户有admin角色或用户的Principal信息保存的是sardine用户名,那么添加权限
        Subject subject= SecurityUtils.getSubject();
        if(subject.hasRole("admin")||subject.getPrincipal().equals("sardine")) {
            Info.addStringPermission("admin:play");
        }
        return Info;
    }

PS:这里的addStringPermission里面的参数,可以改成通过自动装配注解,从数据库取权限名称。

在controller层也可以写注解来限制请求

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值