Teach you Shiro to integrate SpringBoot and avoid all kinds of pits (Shandong Shuman Rivers and Lakes)

Dependency package

<dependency>
	<groupId>org.apache.shiro</groupId>
	<artifactId>shiro-spring</artifactId>
	<version>1.3.2</version>
</dependency>

Database Table

Keep everything simple, user user table, and role role table

user

 

 

role

 

Shiro related classes

Shiro configuration class

@Configuration
public class ShiroConfig {
    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 必须设置 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // setLoginUrl 如果不设置值,默认会自动寻找Web工程根目录下的"/login.jsp"页面 或 "/login" 映射
        shiroFilterFactoryBean.setLoginUrl("/notLogin");
        // 设置无权限时跳转的 url;
        shiroFilterFactoryBean.setUnauthorizedUrl("/notRole"); // 设置拦截器 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); //游客,开发权限 filterChainDefinitionMap.put("/guest/**", "anon"); //用户,需要角色权限 “user” filterChainDefinitionMap.put("/user/**", "roles[user]"); //管理员,需要角色权限 “admin” filterChainDefinitionMap.put("/admin/**", "roles[admin]"); //开放登陆接口 filterChainDefinitionMap.put("/login", "anon"); //其余接口一律拦截 //主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截 filterChainDefinitionMap.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); System.out.println("Shiro拦截器工厂类注入成功"); return shiroFilterFactoryBean; } /** * 注入 securityManager */ @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 设置realm. securityManager.setRealm(customRealm()); return securityManager; } /** * 自定义身份认证 realm; * <p> * 必须写这个类,并加上 @Bean 注解,目的是注入 CustomRealm, * 否则会影响 CustomRealm类 中其他类的依赖注入 */ @Bean public CustomRealm customRealm() { return new CustomRealm(); } } 

Note : The SecurityManager class inside should be imported. import org.apache.shiro.mgt.SecurityManager;However , if you copy the code, it will be imported by default java.lang.SecurityManager. It is also a little pitted here. Other classes belong to the classes in the shiro package.

The shirFilter method mainly sets some important jump urls, such as jumping when not logged in and without permission; and setting up permission interception for various urls, such as urls starting with /user require user permissions, and urls starting with /admin url requires admin permissions, etc.

Permission Intercept Filter

When running a web application, Shiro will create some useful default Filter instances and automatically make them available. These default Filter instances are defined by the DefaultFilter enumeration class. Of course, we can also customize Filter Examples, which will be covered in future articles

DefaultFilter

 

Filter explain
anon No parameters, open permissions, can be understood as anonymous users or tourists
authc No parameters, authentication required
logout No parameters, log out, after execution, it will jump directly to the shiroFilterFactoryBean.setLoginUrl();set url
authcBasic No parameter, it means httpBasic authentication
user No parameter, indicating that there must be a user, no check is performed when logging in
ssl No parameters, indicating a secure URL request, the protocol is https
perms[user] Multiple parameters can be written, which means that one or some permissions are required to pass. When there are multiple parameters, write perms["user, admin"]. When there are multiple parameters, each parameter must pass to be passed.
roles[admin] Multiple parameters can be written, indicating that only one or some roles can pass. When there are multiple parameters, write roles["admin, user"]. When there are multiple parameters, each parameter must pass to be passed.
rest[user] According to the requested method, it is equivalent to perms[user:method], where method is post, get, delete, etc.
port[8081] When the requested URL port is not 8081, jump to schema://serverName:8081?queryString where schmal is the protocol http or https, etc., serverName is the Host you are visiting, 8081 is the Port port, and queryString is the URL you are visiting The parameter after the ?

The most commonly used are anon, authc, user, roles, perms, etc.

Note : anon, authc, authcBasic, user are the first group of authentication filters, perms, port, rest, roles, ssl are the second group of authorization filters. You need to complete the authentication before you can go to find the authorization) before you can go to the second group of authorizers (for example, to access the url that requires roles permission, if you have not logged in, it will jump directly to the shiroFilterFactoryBean.setLoginUrl();set url )

Custom realm class

We first have to inherit the AuthorizingRealm class to customize our own realm for our custom identity and authority authentication operations. Remember to override the doGetAuthenticationInfo and doGetAuthorizationInfo methods (the names of the two methods are very similar, don't make a mistake)

public class CustomRealm extends AuthorizingRealm {
    private UserMapper userMapper;

    @Autowired
    private void setUserMapper(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    /**
     * 获取身份验证信息
     * Shiro中,最终是通过 Realm 来获取应用程序中的用户、角色及权限信息的。
     *
     * @param authenticationToken 用户身份信息 token
     * @return 返回封装了用户信息的 AuthenticationInfo 实例
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("————身份认证方法————");
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        // 从数据库获取对应用户名密码的用户
        String password = userMapper.getPassword(token.getUsername());
        if (null == password) { throw new AccountException("用户名不正确"); } else if (!password.equals(new String((char[]) token.getCredentials()))) { throw new AccountException("密码不正确"); } return new SimpleAuthenticationInfo(token.getPrincipal(), password, getName()); } /** * 获取授权信息 * * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("————权限认证————"); String username = (String) SecurityUtils.getSubject().getPrincipal(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //获得该用户角色 String role = userMapper.getRole(username); Set<String> set = new HashSet<>(); //需要将 role 封装到 Set 作为 info.setRoles() 的参数 set.add(role); //设置该用户拥有的角色 info.setRoles(set); return info; } } 

The two rewritten methods are to implement identity authentication and authority authentication. Shiro has a Subject.login()method . When we pass in the token that encapsulates the user name and password as parameters, it will run into these two methods. (Not necessarily both methods will enter)

The doGetAuthorizationInfo method will only be used when authorization authentication is required, such as filterChainDefinitionMap.put("/admin/**", "roles[admin]");the When entering /admin, the doGetAuthorizationInfo method will be used to check the authorization; and the doGetAuthenticationInfo method is required when authentication is required ( such as the previous Subject.login()method ) will enter

Let's talk about the UsernamePasswordToken class, we can get the user name and password when logging in from this object (which will be used when logging in new UsernamePasswordToken(username, password);), and there are the following methods to get the user name or password

token.getUsername()  //获得用户名 String
token.getPrincipal() //获得用户名 Object 
token.getPassword()  //获得密码 char[]
token.getCredentials() //获得密码 Object

Note : Many people will find that the interface of UserMapper and other classes cannot be injected through @Autowired, and NullPointerException will be reported when running the program. There are many reasons such as the Spring loading order on the Internet, but there is actually a very important place for everyone Note that the CustomRealm class is set in the securityManager.setRealm()method , and many people write it directly securityManager.setRealm(new CustomRealm());. This is not possible. You must use @Bean to inject MyRealm, and you cannot directly new objects:

    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置realm.
        securityManager.setRealm(customRealm());
        return securityManager;
    }

    @Bean
    public CustomRealm customRealm() { return new CustomRealm(); } 

The reason is also very simple. Like calling Service in the Controller, they are all SpringBeans and cannot be new by themselves.

Of course, the same logic can also be written like this:

    @Bean
    public SecurityManager securityManager(CustomRealm customRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置realm.
        securityManager.setRealm(customRealm);
        return securityManager;
    }

Then just add an annotation like @Component to the CustomRealm class

Function realization

The functions of this article are all implemented by the interface returning json data

Assign controller based on url permissions

游客
@RestController
@RequestMapping("/guest")
public class GuestController{
    @Autowired    
    private final ResultMap resultMap;

    @RequestMapping(value = "/enter", method = RequestMethod.GET)
    public ResultMap login() {
        return resultMap.success().message("欢迎进入,您的身份是游客"); } @RequestMapping(value = "/getMessage", method = RequestMethod.GET) public ResultMap submitLogin() { return resultMap.success().message("您拥有获得该接口的信息的权限!"); } } 
普通登陆用户
@RestController
@RequestMapping("/user")
public class UserController{
    @Autowired    
    private final ResultMap resultMap;

    @RequestMapping(value = "/getMessage", method = RequestMethod.GET)
    public ResultMap getMessage() {
        return resultMap.success().message("您拥有用户权限,可以获得该接口的信息!"); } } 
管理员
@RestController
@RequestMapping("/admin")
public class AdminController {
    @Autowired    
    private final ResultMap resultMap;

    @RequestMapping(value = "/getMessage", method = RequestMethod.GET)
    public ResultMap getMessage() {
        return resultMap.success().message("您拥有管理员权限,可以获得该接口的信息!"); } } 

Suddenly noticed that the AccountException exception was thrown in the CustomRealm class, now create a class to catch the exception

@RestControllerAdvice
public class ExceptionController {
    private final ResultMap resultMap;

    @Autowired
    public ExceptionController(ResultMap resultMap) {
        this.resultMap = resultMap;
    }

    // 捕捉 CustomRealm 抛出的异常
    @ExceptionHandler(AccountException.class)
    public ResultMap handleShiroException(Exception ex) {
        return resultMap.fail().message(ex.getMessage());
    }
}

Of course, there is also the LoginController for login and other processing

@RestController
public class LoginController {
    @Autowired    
    private ResultMap resultMap;
    private UserMapper userMapper;

    @RequestMapping(value = "/notLogin", method = RequestMethod.GET)
    public ResultMap notLogin() {
        return resultMap.success().message("您尚未登陆!"); } @RequestMapping(value = "/notRole", method = RequestMethod.GET) public ResultMap notRole() { return resultMap.success().message("您没有权限!"); } @RequestMapping(value = "/logout", method = RequestMethod.GET) public ResultMap logout() { Subject subject = SecurityUtils.getSubject(); //注销 subject.logout(); return resultMap.success().message("成功注销!"); } /** * 登陆 * * @param username 用户名 * @param password 密码 */ @RequestMapping(value = "/login", method = RequestMethod.POST) public ResultMap login(String username, String password) { // 从SecurityUtils里边创建一个 subject Subject subject = SecurityUtils.getSubject(); // 在认证提交前准备 token(令牌) UsernamePasswordToken token = new UsernamePasswordToken(username, password); // 执行认证登陆 subject.login(token); //根据权限,指定返回数据 String role = userMapper.getRole(username); if ("user".equals(role)) { return resultMap.success().message("欢迎登陆"); } if ("admin".equals(role)) { return resultMap.success().message("欢迎来到管理员页面"); } return resultMap.fail().message("权限错误!"); } } 

test

 

Access information interface before login

 

 

Ordinary user login

 

 

wrong password

 

 

Admin login

 

 

getting information

 

 

getting information

 

 

log out



Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325218341&siteId=291194637