springboot combined with shiro actual combat - identity authentication

 

Table of contents

Environment build

Configure shiro environment

Summarize


Hello, everyone, when we write any enterprise-level project, we basically need permissions, which include identity authentication and authorization.

The so-called identity authentication is to prove that you are you.

The so-called authorization is to understand what you can do after logging in.

Now, let's start with the springboot project and combine it with the shiro framework to do it all.

Environment build

Basic Information:

springboot version:

Initial other dependencies:

Created:

Add dependencies for Shiro and Spring Boot. Add the following dependencies to your pom.xml file:

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

Configure shiro environment

The first step is to create a Shiro configuration class.

Create a class and mark it with @Configuration annotation. In this class, you can configure related settings of Shiro, such as Realm, Session manager, etc.

The configuration code is as follows:

package com.it.shirodemo.config;

import com.it.shirodemo.realm.MyRealm;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ShiroConfig {

    @Bean
    public MyRealm myRealm(){
        return new MyRealm();
    }


    @Bean(name = "mySecurityManager")
    public DefaultWebSecurityManager  securityManager(@Qualifier("myRealm") MyRealm myRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm);
        return securityManager;
    }

    /**
     * 路径过滤规则
     * @return
     */
    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition() {
        DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
        //登录不需要校验权限
        chainDefinition.addPathDefinition("/login", "anon");
        //其他任何url都需要校验是否登录
        chainDefinition.addPathDefinition("/**", "authc");
        return chainDefinition;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("mySecurityManager") SecurityManager securityManager) {
        ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
        filterFactoryBean.setSecurityManager(securityManager);
        filterFactoryBean.setFilterChainDefinitionMap(shiroFilterChainDefinition().getFilterChainMap());
        return filterFactoryBean;
    }

    /**
     * 开启Shiro注解模式,可以在Controller中的方法上添加注解
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("mySecurityManager") SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
}

These are relatively fixed codes, and you don't even need to know the meaning of each bean. It is no problem to copy the past and use it directly.

Then, we need to customize a Realm.

the code

package com.it.shirodemo.realm;

import com.it.shirodemo.entity.User;
import com.it.shirodemo.service.UserService;
import org.apache.shiro.SecurityUtils;
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.subject.Subject;

import javax.annotation.Resource;


public class MyRealm extends AuthorizingRealm {

    @Resource
    private UserService userService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // 实现授权逻辑
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //获得当前subject
        Subject subject = SecurityUtils.getSubject();
        //获得当前的principal,也就是认证完后我们放入的信息
        User currentUser = (User) subject.getPrincipal();
        //添加权限
        info.addStringPermissions(currentUser.getPerms());

        return info;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 实现认证逻辑
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
        //从数据库中查询该用户
        String username = usernamePasswordToken.getUsername();
        User user = userService.queryUserByName(username);
        //如果不存在该用户,返回一个空错误,前端也可以相应显示提示
        if (user == null) {
            return null;
        }
        // 第一个参数为principal,就是授权方法里面拿到的那个对象;
        // 第二个参数为从数据库中查出的用于验证的密码,shiro中密码验证不需要我们自己去做;
        // 第三个参数为 realmName
        return new SimpleAuthenticationInfo(user,user.getPwd(),getName());
    }
}

It is mainly to write authorization and authentication methods. The above is doGetAuthorizationInfoused for authorization. It will be called every time you visit a certain url of the system. The goal is to obtain the current user's permissions.

Take a look at the User class:

the code

package com.it.shirodemo.entity;

import lombok.Data;
import java.util.Set;

@Data
public class User {
    private String userName;
    private String pwd;
    private Set<String> perms;
}

perms is a Set collection, we initialize and query in the simulated service:

package com.it.shirodemo.service;

import com.it.shirodemo.entity.User;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class UserService {


    public User queryUserByName(String username) {
        List<User> users = new ArrayList<>();
        users.add(new User(){
   
   {
            setUserName("admin");
            setPwd("123");
            setPerms(new HashSet<String>(){
   
   {
                add("shiro:user-query");
                add("shiro:user-add");
                add("shiro:user-delete");
                add("shiro:user-edit");
            }});
        }});

        users.add(new User(){
   
   {
            setUserName("zhangsan");
            setPwd("123");
            setPerms(new HashSet<String>(){
   
   {
                add("shiro:user-query");
            }});
        }});
        List<User> userList = users.stream().filter(e -> e.getUserName().equals(username)).collect(Collectors.toList());
        if(userList.size() > 0){
            return userList.get(0);
        }
        return null;

    }
}

For testing, we specially made a user's Controller

package com.it.shirodemo.controller;

import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/v1/user/")
public class UserController {

    @GetMapping("/query")
    @RequiresPermissions("shiro:user-query")
    public String query(){
       return "用户查询";
    }

    @GetMapping("/edit")
    @RequiresPermissions("shiro:user-edit")
    public String edit(){
        return "用户修改";
    }

    @GetMapping("/delete")
    @RequiresPermissions("shiro:user-delete")
    public String delete(){
        return "用户删除";
    }

    @GetMapping("/add")
    @RequiresPermissions("shiro:user-add")
    public String add(){
        return "用户新增";
    }
}

We expect that the admin user can access all permissions, and zhangsan can only access the interface that the user queries.

Executed

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    // 实现授权逻辑
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    //获得当前subject
    Subject subject = SecurityUtils.getSubject();
    //获得当前的principal,也就是认证完后我们放入的信息
    User currentUser = (User) subject.getPrincipal();
    //添加权限
    info.addStringPermissions(currentUser.getPerms());

    return info;
}

After this authorization method, the returned authorization information object has a list of all permissions. If it contains your current access permissions, for example, shiro:user-addaccess is allowed, otherwise access is not allowed.

Let's look at the authentication method again. To put it bluntly, authentication is login verification.

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    // 实现认证逻辑
    UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
    //从数据库中查询该用户
    String username = usernamePasswordToken.getUsername();
    User user = userService.queryUserByName(username);
    //如果不存在该用户,返回一个空错误,前端也可以相应显示提示
    if (user == null) {
        return null;
    }
    // 第一个参数为principal,就是授权方法里面拿到的那个对象;
    // 第二个参数为从数据库中查出的用于验证的密码,shiro中密码验证不需要我们自己去做;
    // 第三个参数为 realmName
    return new SimpleAuthenticationInfo(user,user.getPwd(),getName());
}

Here is a very interesting thing, the SimpleAuthenticationInfo object that is finally returned, the first parameter of the construction method is an Object type.

This thing is actually:

In other words, what parameters do you pass in the authentication method, and what object is the principal obtained in the authorization method.

Why provide such an opening for you to pass the object, in fact, it is to allow you to obtain the authorization list of the current user when authorizing!

I have seen many articles about shiro on the Internet, and my understanding of this point is rather vague. Anyway, you just remember that no matter what you pass in this parameter, you must be able to get the permission list through it. For example, I will pass a User object here, and there are already perms in the user object, so there is no problem. You can also pass the username, and then go to the database to query the permission list when authorizing, and it is completely fine.

Finally, give the login method:

package com.it.shirodemo.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class LoginController {

    @GetMapping("/login")
    public String login(String username,String password){
        Subject subject = SecurityUtils.getSubject();
        //令牌
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);

        try {
            //登录认证
            subject.login(token);
            return "恭喜,登录成功!";
        }catch (UnknownAccountException e){
            return "账号不存在";
        }catch (IncorrectCredentialsException e){
            return "密码错误";
        }

    }
}

To verify, open the browser, because it is a Get request, we use the browser to send it directly.

http://localhost:8080/login?username=admin&password=123

Then go to visit: http://localhost:8080/v1/user/add

If Zhang San logs in, visit http://localhost:8080/v1/user/add

The background reported an error:

org.apache.shiro.authz.AuthorizationException: Not authorized to invoke method: public java.lang.String com.it.shirodemo.controller.UserController.add()

Summarize

This article implements the so-called ACL permission control, which is implemented with the shiro framework combined with springboot, which is very suitable for beginners to learn.

Source code download https://gitee.com/skyblue0678/shiro-demo

Guess you like

Origin blog.csdn.net/weixin_39570751/article/details/132317373