渐进式 shiro - shiro + jwt+salt (一)

本文简述:shiro基本使用

shiro 最常用得就是用来 处理登录接口 以及 处理其他需要在请求头中携带token的接口 这么一个框架。

Shiro 架构设计:

  • Authentication: 认证。如用户的登录。
  • Authorization: 授权。用户是否有权限访问指定 URL 等。
  • Cryptography: 密码学。如密码的加密。
  • Session Management: Session 管理。
  • Web Integration: Web 集成。Shiro 不依赖于容器。

shiro 登录认证流程:

  1. 用户访问登录接口 /login, 输入登录账号和密码被封装成 UsernamePasswordToken 对象.
  2. 接下来,登录服务中调用 subject.login() 方法,Shiro 立即进入用户认证过程,此认证过程做的事情式: 检验用户账号和密码是否正确

例子:使用 shiro.ini 固定配置文件

上面我们简述了 shiro 的结构以及最最简单得认证流程,你可能不太理解,不过没有关系,举个例子说明。

想一下我们最最简单的登录流程是怎么样的?用户输入用 户名和密码,然后访问登录接口,我们在 "/login"接口中接收到数据后,与数据库中的用户进行查找比较,返回该用户是否输入了正确的账号和密码。

我们先创建一个简单的示例来说明:

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.10.0</version>
</dependency>

创建一个用户信息文件: src/main/resources/shiro.ini ,此文件可以理解为 数据库

# ---------------------
# users用来定义用户
[users]
# 用户名 = 密码,角色1,角色2...
admin = 123456, admin
guest = guest, guest
aa = 123456, guest
# ---------------------
# roles用来定义角色
[roles]
# 角色 = 权限 (* 代表所有权限)
admin = *
# 角色 = 权限 (* 代表所有权限)
guest = select
aa = update

登录

@SpringBootTest
class ShiroTest {

    @Test
    public void testShiroAuth(){
        // 1.创建安全管理器
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        // 2.设置 realm
        securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
        // 3.设置安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        // 4.获取当前主体对象
        Subject subject = SecurityUtils.getSubject();
        // 5.将用户登录时的账号和密码封账成 UsernamePasswordToken 对象.
        // 此处写入 "admin", "password",表示用户输入的账号,密码
        UsernamePasswordToken token = new UsernamePasswordToken("admin", "password");

        try {
            System.out.println("登录认证前状态:" + subject.isAuthenticated());
            subject.login(token);
            System.out.println("登录认证后状态:" + subject.isAuthenticated());

            boolean permitted = subject.isPermitted("update");
            System.out.println(permitted);
        } catch (UnknownAccountException e) {
            System.out.println("用户不存在");
        } catch (IncorrectCredentialsException e) {
            System.out.println("账号或密码不正确");
        }
    }
}

shiro内部提供了两种方法创建 Realm 对象,这个小栗子中我们通过shiro提供得类IniRealm来读取文件 shiro.ini中写死得数据:

  1. 通过配置文件创建 Realm 对象: IniRealm 内封装了通过配置文件读取用户信息得方法,将此实例交给 SecurityManager 管理.
  2. 通过 springboot 中注解方式创建 Realm 对象: 实现接口 AuthorizingRealm,并在其 doGetAuthenticationInfo 方法中对登录信息进行校验处理 ,最终注入为 Bean

springboot 中使用 shiro

上面的代码,我们在一个测试方法中模拟了 shiro 的整个认证流程。
但是如果要在 springboot 中集成 shiro,可没有这么简单,我们需要将上面的 1,2,3,4,5 步骤分别拆开在不同的文件中,以达到更好的解耦性以及可扩展性,其实就是按照 java 的标准来。

因此我们首先需要了解一点 shiro 配置相关的东西。

shiro 的配置类主要包含三个 bean:

  • SecurityManager:安全管理器
  • shiroFilter:过滤器
  • Realm:realm 认证流程
  1. shiro 作为一个用来管理 http 请求框架,里面有非常多细节的功能,哪些功能启用,哪些功能不启用,是由控制中枢决定: SecurityManager
  2. shiro 集成到一个 springboot 项目中,而项目有许许多多的接口,哪些接口无需登录可以直接访问,哪些接口必须登录后带着 token 访问,这是由过滤器决定: shiroFilter
  3. shiro 作为一个通用的认证授权框架,并非只能在 springboot 中使用,因此框架专门将认证流程抽象后独立出来,产生了一个新的的概念:Realm.

到这里,shiro 中的三个核心概念你应该有所了解了,剩下的内容就式关于如何使用shiro: 在 springboot 中如何集成 shiro?

springboot 集成 shiro

在 springboot 中集成 shiro,还得再赘述几句。

spring 是用来管理 javaBean 对象的容器,集成 shiro 时,shiro 也同样要将上面提到的几个核心对象交给 spring 去管理。

先添加依赖

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-web-starter</artifactId>
    <version>1.10.0</version>
</dependency>

首先是配置,shiro 的三大核心,通过 @Bean 注册为spring中的bean对象交给 spring 去管理

@Configuration
public class ShiroConfig {
    /**
     * `SecurityManager`:安全管理器
     */
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(userRealm());
        return securityManager;
    }

    /**
     * `shiroFilter`:过滤器
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,ShiroFilterChainDefinition shiroFilterChainDefinition) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // Shiro的核心安全接口,这个属性是必须的
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        // 定义过滤链
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // 登录,不需要拦截的访问
        filterChainDefinitionMap.put("/login", "anon");
        // 其余所有接口都需要认证拦截
        filterChainDefinitionMap.put("/**","authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }
    /**
     * `Realm`:realm 认证流程
     */
    @Bean
    public UserRealm userRealm() {
        return new UserRealm();
    }
}

在上面得演示代码中有报错,这是因为 UserRealm还不知道在哪里。回顾一下第一个的简单例子,我们是从shiro.ini这个文件中读取的用户数据,由shiro将文件中得数据封装成了Realm.

在实际项目中,我们需要从数据库中读取用户信息然后再比较,这个过程就需要我们自己来写。

如何做呢?实现AuthorizingRealm即可。

注意,userService 使用 mybatis 生成。

public class UserRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    /**
     * 授权
     *
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    /**
     * 验证:用户登录
     * 
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken accessToken = (UsernamePasswordToken) authenticationToken;

        // 查询用户
        UserEntity user = Optional.ofNullable(userService.getOne(new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getUsername, accessToken.getUsername())))
                .orElseThrow(() -> new UnknownAccountException("账号或密码不正确"));

        //  验证密码
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                user,
                user.getPassword(),
                getName()
        );
        return authenticationInfo;

    }
}

我们实现 UserRealm 就是实现认证过程,里面有两个方法,单词十分的相近,并且登录认证使用下面那个 doGetAuthenticationInfo 方法.

  1. 在这一步中,doGetAuthenticationInfo 方法.有一个入参类型AuthenticationToken,它就是登陆时用户传入的账号和密码。没错,一旦我们重写之后,调用subject.login(),就会立即执行这一方法中的代码,你可以在此处打个断点感受一下。

  2. 我们将其向下转型,转换为 UsernamePasswordToken 这个类。UsernamePasswordTokenAuthenticationToken的一个实现类,比它多了若干的方法。

  3. 转换的目的是UsernamePasswordToken提供了一个获取用户名的方法,我们调用此方法拿到用户传过来的 username ,根据此 username 去查询数据库

  4. 接下来我们使用 shiro提供的类 SimpleAuthenticationInfo构造一个对象并返回。这里你一定会感到疑惑,怎么就直接返回了一个对象呢?正常来说,从数据库查到了用户密码后,应该进行比较啊,equal()呢,x == y 呢?

  5. shiro 为了将比较结果存储起来,将比较这一步骤也给封装起来了: 因此只要在方法中返回了 SimpleAuthenticationInfo这个类,接下来shiro就会在内部自动执行比较操作。

好的,接下来,你可以创建一个 controller,在数据库里创建一个用户,完整感受一下。

@RestController
public class UserController {
    @PostMapping("/login")
    public AjaxResult loginUser(@RequestBody UserEntity userVo) {
        Subject subject = SecurityUtils.getSubject();

        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userVo.getUsername(), userVo.getPassword());
        usernamePasswordToken.setRememberMe(true);
        try {
            subject.login(usernamePasswordToken);
            System.out.println("登录成功");
        } catch (AuthenticationException ae) {
            System.out.println("登录失败: " + ae.getMessage());
            return AjaxResult.error("登录失败: " + ae.getMessage());
        }
        return AjaxResult.success(blogDto);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Shiro是一个Java安全框架,可以提供身份验证、授权、加密和会话管理等功能,Spring Boot是一个快速开发框架,可以帮助开发人员更快地构建和部署应用程序,JWT(JSON Web Token)是一种轻量级的身份验证和授权机制。将这三个框架结合起来,可以构建一个安全的Web应用程序。 以下是一个简单的Shiro+Spring Boot+JWT项目的实现步骤: 1.创建一个Spring Boot项目,并添加ShiroJWT依赖项: ``` <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.7.1</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.10.7</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.10.7</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.10.7</version> <scope>runtime</scope> </dependency> ``` 2.创建一个Shiro配置类,配置Shiro的安全策略和过滤器链: ``` @Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); shiroFilterFactoryBean.setLoginUrl("/login"); shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized"); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); filterChainDefinitionMap.put("/login", "anon"); filterChainDefinitionMap.put("/**", "jwt"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } @Bean public DefaultWebSecurityManager securityManager(Realm realm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(realm); return securityManager; } @Bean public Realm realm() { return new UserRealm(); } @Bean public JwtFilter jwtFilter() { return new JwtFilter(); } } ``` 3.创建一个自定义Realm类,实现Shiro的认证和授权逻辑: ``` public class UserRealm extends AuthorizingRealm { @Autowired private UserService userService; @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); User user = (User) principals.getPrimaryPrincipal(); authorizationInfo.addRole(user.getRole()); return authorizationInfo; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String username = (String) token.getPrincipal(); User user = userService.findByUsername(username); if (user == null) { throw new UnknownAccountException(); } return new SimpleAuthenticationInfo(user, user.getPassword(), getName()); } } ``` 4.创建一个JwtFilter类,实现JWT的认证逻辑: ``` public class JwtFilter extends AuthenticatingFilter { @Autowired private UserService userService; @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { HttpServletRequest httpServletRequest = (HttpServletRequest) request; String token = httpServletRequest.getHeader("Authorization"); if (StringUtils.isEmpty(token)) { throw new UnauthorizedException(); } JwtToken jwtToken = new JwtToken(token); try { getSubject(request, response).login(jwtToken); } catch (AuthenticationException e) { throw new UnauthorizedException(); } return true; } @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { return false; } } ``` 5.创建一个JwtToken类,实现JWTToken逻辑: ``` public class JwtToken implements AuthenticationToken { private String token; public JwtToken(String token) { this.token = token; } @Override public Object getPrincipal() { return JwtUtils.getSubject(token); } @Override public Object getCredentials() { return token; } } ``` 6.创建一个UserController类,实现用户登录和获取用户信息的逻辑: ``` @RestController public class UserController { @Autowired private UserService userService; @PostMapping("/login") public Result login(@RequestBody User user) { String token = userService.login(user); return Result.success(token); } @GetMapping("/user") public Result getUserInfo() { User user = (User) SecurityUtils.getSubject().getPrincipal(); return Result.success(user); } } ``` 7.创建一个UserService类,实现用户登录和生成JWT Token的逻辑: ``` @Service public class UserService { @Autowired private UserMapper userMapper; public User findByUsername(String username) { return userMapper.findByUsername(username); } public String login(User user) { User realUser = findByUsername(user.getUsername()); if (realUser == null || !realUser.getPassword().equals(user.getPassword())) { throw new UnauthorizedException(); } return JwtUtils.generateToken(realUser.getId(), realUser.getUsername(), realUser.getRole()); } } ``` 8.创建一个JwtUtils类,实现JWTToken生成和解析逻辑: ``` public class JwtUtils { private static final String SECRET = "secret"; private static final long EXPIRATION_TIME = 86400000; // 24 hours public static String generateToken(String id, String username, String role) { Date now = new Date(); Date expirationDate = new Date(now.getTime() + EXPIRATION_TIME); return Jwts.builder() .setId(id) .setSubject(username) .claim("role", role) .setIssuedAt(now) .setExpiration(expirationDate) .signWith(SignatureAlgorithm.HS512, SECRET) .compact(); } public static String getSubject(String token) { return Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(token) .getBody() .getSubject(); } } ``` 这样,就可以使用Shiro+Spring Boot+JWT构建一个安全的Web应用程序了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值