使用springboot整合shiro进行登录认证(md5+盐值+散列次数)

发布于:2024-04-18 ⋅ 阅读:(33) ⋅ 点赞:(0)

准备工作:
md5加密算法:

public class md5Test {
    @Test
    public void md5() {
        //明文(123) + 盐值(monian) + 加密次数(1024)    ==>  密文  8ea680082c12d7d878a3a97214ebbdc2
        Md5Hash md5Hash = new Md5Hash("123","monian",1024);
        System.out.println(md5Hash);
    }
}

创建Springboot项目web、Lombok:

由于我们使用的是jsp,需要引入相关的依赖(防止访问页面时,访问的是资源路径(访问后不解析,会直接下载文件))

此外还有shiro依赖、mybatis-plus、mysql、druid:

 <!--jsp-->
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>
        <!--shiro-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-starter</artifactId>
            <version>1.5.3</version>
        </dependency>
<!--    mybatis-plus    -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
<!--    mysql    -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
<!--    druid    -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.6</version>
        </dependency>

properties.yml配置文件: 

server:
  port: 8080
  servlet:
    context-path: /shiro
spring:
  mvc:
    view:
      prefix: /
      suffix: .jsp
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    password: chen
    username: root
    url: jdbc:mysql://localhost:3306/shiro?useSSL=false&userUnicode=true&characterEncoding=utf8
    druid:
      max-active: 10
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  type-aliases-package: net.wanho.entity
  mapper-locations: classpath:mapper/*.xml

创建Realm(数据源)对象:

/**
 * 自定义Realm ,继承AuthorizingRealm
 */
public class ShiroRealm extends AuthorizingRealm {

    @Resource
    private UserService userService;

    /**
     * 认证
     * Authentication 证明真实性,鉴定;身份验证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //先怼死 不要从数据库找数据 admin 123
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
        //用户输入用户名
        String username = usernamePasswordToken.getUsername();
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("username",username);
        User user = userService.getOne(wrapper);
        //账号不存在
        if(ObjectUtils.isEmpty(user)){
            throw new UnknownAccountException("账号不存在!!!");
        }

//        String password = (String) usernamePasswordToken.getCredentials();
//        if (!password.equals("8ea680082c12d7d878a3a97214ebbdc2")){
//            throw new IncorrectCredentialsException("账号密码错误!!!");
//        }

        //密文密码
        //存放盐值
        ByteSource byteSource = ByteSource.Util.bytes(user.getSalt());
        return new SimpleAuthenticationInfo(username,user.getPassword(),byteSource,super.getName());
    }

    /**
     * 授权
     * Authorization 批准书,授权书;批准
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
}

添加shiro的配置:

@Configuration
public class ShiroConfig {

    /**
     *  获得认证的数据源
     *  realm 领域、范围
     *  @return
     */
    @Bean
    public Realm getRealm() {
        ShiroRealm realm = new ShiroRealm();
        //使用 md5 加密
        //创建密码匹配器,支持散列算法
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        credentialsMatcher.setHashAlgorithmName("MD5"); //设置加密算法
        credentialsMatcher.setHashIterations(1024); //设置散列次数
        //设置密码匹配器
        realm.setCredentialsMatcher(credentialsMatcher);
        return realm;
    }

    /**
     * 创建安全管理器 类似于: 自定义XXService
     * 自定义的Realm交给SecurityManager管理
     * @param realm
     * @return
     */
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm);
        return securityManager;
    }

    /**
     * ShiroFilter ,对资源进行过滤处理
     * 将SecurityManager交给ShiroFilterFactoryBean管理
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
        //设置过滤路径 anon 默认 authc 认证 roles perms 授权 logout 退出
        Map<String,String> map = new LinkedHashMap<>();
        map.put("/index.jsp","authc");
        map.put("/login.jsp","anon");
        //设置安全管理器
        filterFactoryBean.setSecurityManager(securityManager);
        filterFactoryBean.setFilterChainDefinitionMap(map);
        filterFactoryBean.setLoginUrl("/login.jsp");
        return filterFactoryBean;
    }
}

配置mybatis-plus的配置:

/**
 * 配置类: 类似于 spring => *.xml
 */
@Configuration
@MapperScan("net.wanho.mapper")
public class MybatisPlusConfig {
    /**
     * 配置拦截器
     * @return
     */
    @Bean
    public MybatisPlusInterceptor configMybatisPlusInterceptor(){
        //创建拦截器实例
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        //添加 分页拦截器 指定数据库
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

添加实体:

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_user")
public class User implements Serializable {
    private Integer id;
    private String username;
    private String password;
    private String salt;
}

mapper:

public interface UserMapper extends BaseMapper<User> {
    User selectUsernameAndPassword(String username);
}

service:

public interface UserService extends IService<User> {
}
@Service
@Transactional(rollbackFor = Exception.class)
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

controller:

@Controller
@RequestMapping("/user")
public class UserController {

    @PostMapping("/login")
    public String login(String username, String password, Model model){
        try {
            //获得用户主体
            Subject subject = SecurityUtils.getSubject();
            //封装 token
            UsernamePasswordToken token = new UsernamePasswordToken(username, password);
            //调用登录
            subject.login(token);
            //subject.checkPermission("user:view");
            //跳转到主页
            return "index";
        } catch (Exception e) {
            //设置错误消息
            model.addAttribute("msg","账号或密码错误!!!") ;
            //跳到到登录页面
            return "login";
        }
    }

    @GetMapping("/logout")
    public String logout(){
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return "redirect:/login.jsp";
    }
}

注意注解!!!,不能使用@RestController,返回的是json/xml文件,不会进行解析;

前端界面:

index.jsp:

<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<%@page contentType="text/html;UTF-8" pageEncoding="UTF-8" isErrorPage="false" %>
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<%--受限资源--%>
<h1>系统主页</h1>
<h2>欢迎: <shiro:principal/> </h2>
<a href="${pageContext.request.contextPath}/user/logout">安全退出</a>
<h1>系统主页</h1>

<ul>
    <li><a href="#">用户管理</a></li>
    <li><a href="#">商品管理</a></li>
    <li><a href="#">订单管理</a></li>
    <li><a href="#">物流管理</a></li>
</ul>
</body>
</html>

 login.jsp:

<%@page contentType="text/html;UTF-8" pageEncoding="UTF-8" isErrorPage="false" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<style>
    .massage{
     color: red;
    }
</style>
<body>
<h1>登录界面</h1>
<a href="${pageContext.request.contextPath}/user/logout">安全退出</a>
<form action="${pageContext.request.contextPath}/user/login" method="post">
    用户名:<input type="text" name="username" > <br/>
    密码:<input type="password" name="password"> <br>
    <input type="submit" value="登录">
</form>
<hr>
<h4 class="massage">${msg}</h4>
</body>
</html>

浏览器测试: 

  

当点击安全退出按钮,默认返回的是login.jsp界面,再次输入index.jsp,也会跳到登陆界面 

知识点梳理:
Spring MVC中内嵌9大成员,使用model可以将数据带到前端界面