Recently I worked on Shiro security framework and found some blog articles on the Internet, but when I came to my own implementation, I encountered all kinds of pits and needed to check the source code and all kinds of tests. This article will show you how to integrate Shiro into SpringBoot and avoid the glitches. This time, basic login and character permissions are implemented. Later articles will also explain other features, such as Shiro + SpringBoot integration JWT.

Source code: github.com/HowieYuan/s…

Depend on the package

< the dependency > < groupId > org, apache shiro < / groupId > < artifactId > shiro - spring < / artifactId > < version > 1.3.2 < / version > </dependency>Copy the code

The database table

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

Shiro related classes

Shiro configuration class

@Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); / / must be set SecurityManager shiroFilterFactoryBean. SetSecurityManager (SecurityManager); //setIf the value of LoginUrl is not set, the system automatically searches for the url in the root directory of the Web project by default"/login.jsp"The page or"/login"Mapping shiroFilterFactoryBean. SetLoginUrl ("/notLogin"); // Set the unrestricted jump url; shiroFilterFactoryBean.setUnauthorizedUrl("/notRole"); // Set interceptor Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); / / visitors, development authority filterChainDefinitionMap. Put ("/guest/**"."anon"); / / user, need role authorization "user" filterChainDefinitionMap. Put ("/user/**"."roles[user]"); / / administrator, need role authorization "admin" filterChainDefinitionMap. Put ("/admin/**"."roles[admin]"); / / open landing interface filterChainDefinitionMap. Put ("/login"."anon"); / / the rest of the interface are intercepted / / main this line of code that must be on all permissions in the end, or it will lead to all urls are intercepted filterChainDefinitionMap. Put ("/ * *"."authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        System.out.println("Shiro Interceptor Factory class injected successfully");
        returnshiroFilterFactoryBean; } /** * inject securityManager */ @bean public securityManagersecurityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // Set realm.securityManager.setrealm (customRealm());returnsecurityManager; } /** * Custom authentication realm; * <p> * This class must be written and annotated with @bean to inject CustomRealm, * otherwise it will affect dependency injection of other classes in the CustomRealm class */ @bean public CustomRealmcustomRealm() {
        returnnew CustomRealm(); }}Copy the code

Note: the inside of the SecurityManager class import should be import org). Apache shiro. MGT. SecurityManager; But, if you’re copying code, will be the default import Java lang. The SecurityManager pit here also slightly, other classes, also belong to shiro bag inside the class

ShirFilter method is mainly set some important jump URL, such as not logged in, the jump when the right limit; And set all kinds of URL permission interception, such as /user URL needs user permission, /admin url needs admin permission, etc

Permission blocking Filter

When running a Web application, Shiro will create useful DefaultFilter instances defined by the DefaultFilter enumeration class and automatically make them available. We can also customize Filter instances. We’ll talk about that in a future article

Filter explain
anon No parameter, open permissions, can be understood as anonymous user or tourist
authc Authentication is required
logout If no parameter is specified, log outshiroFilterFactoryBean.setLoginUrl();Set the url of the
authcBasic If no parameter is specified, it indicates httpBasic authentication
user If no parameter is specified, the user must exist. No check is performed when the user logs in
ssl It is a secure URL request and the protocol is HTTPS
perms[user] If there are multiple parameters, perms[“user, admin”] is written. If there are multiple parameters, all parameters must pass
roles[admin] When multiple roles are specified, roles[“admin, user”] is specified. When multiple roles are specified, all roles are specified
rest[user] Perms [user:method], where method is POST, get, delete, etc
port[8081] When requesting the URL of the port is not 8081, jump to schemal: / / serverName: 8081? QueryString where schmal is the protocol HTTP or HTTPS or whatever, serverName is the Host that you’re accessing, 8081 is the Port, queryString is the URL that you’re accessing, okay? The following parameters

The common ones are anon, AuthC, User, Roles, perms, etc

Note: Anon, authC, authcBasic, user, perms, port, REST, ROLES, SSL, First to complete the login authentication operation (i.e., to complete the certification before I went to look for authorization) to walk a second set of authorization (need roles such as access url, if you haven’t log in, will jump straight to the shiroFilterFactoryBean. SetLoginUrl (); Set url)

Customize the Realm class

We’ll first customize our own Realm by inheriting the AuthorizingRealm class for our own custom identity, permission authentication operations. Remember to Override the doGetAuthenticationInfo and doGetAuthorizationInfo methods.

public class CustomRealm extends AuthorizingRealm {
    private UserMapper userMapper;

    @Autowired
    private void setUserMapper(UserMapper userMapper) { this.userMapper = userMapper; } /** * Get authentication information * Shiro finally gets user, role, and permission information in the application through Realm. * * @param authenticationToken Indicates the user identity token * @returnReturns an AuthenticationInfo instance that encapsulates user information */ @override protected AuthenticationInfodoGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("———— Authentication method ————"); UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; String password = usermapper.getPassword (token.getUsername()); String password = usermapper.getPassword ();if (null == password) {
            throw new AccountException("Incorrect username");
        } else if(! password.equals(new String((char[]) token.getCredentials()))) { throw new AccountException("Incorrect password");
        }
        returnnew SimpleAuthenticationInfo(token.getPrincipal(), password, getName()); } /** * Obtain authorization information ** @Param principalCollection * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("———— Permission authentication ————"); String username = (String) SecurityUtils.getSubject().getPrincipal(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); String role = usermapper. getRole(username); Set<String>set= new HashSet<>(); // We need to wrap role in Set as the info.setroles () parameter set.add(role); // Set the user's role info.setroles (set);
        returninfo; }}Copy the code

Shiro has a subjection.login () method for login. When we pass in a token that encapsulates the user name and password, we run into these two methods.

The doGetAuthorizationInfo method only when need permission certification will go in, such as configuration in front of the class is configured with filterChainDefinitionMap. Put (“/admin / * * “, “roles/admin”); DoGetAuthorizationInfo = doGetAuthorizationInfo = /admin; The doGetAuthenticationInfo method, on the other hand, is entered only when authentication is required (as in the previous subjy.login () method)

Let’s talk about the UsernamePasswordToken class, from which we can get the username and password for login (new UsernamePasswordToken(username, password);) , and get username or password has the following methods

Token.getusername () // Get the username String token.getPrincipal() // Get the username Object token.getPassword() // Get the password char[] Token.getcredentials () // Obtain the password ObjectCopy the code

Note: A NullPointerException will be declared when the application is running. There are many reasons on the Internet such as Spring loading order, but there is a very important point to note. The CustomRealm class is set in the Shiro configuration class’s SecurityManager.setrealm () method. Many people write securityManager.setrealm (new CustomRealm()) directly. To do this, you must inject MyRealm with @bean instead of new objects directly:

    @Bean
    public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // Set realm.securityManager.setrealm (customRealm());return securityManager;
    }

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

The same way that a Service is called in a Controller, it is a SpringBean and cannot be new by itself

Of course, the same could be said:

@Bean public SecurityManager securityManager(CustomRealm customRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // Set realm.securityManager.setrealm (customRealm);return securityManager;
    }
Copy the code

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

Function implementation

The functions of this article are all realized by the way that the interface returns JSON data

Assign controllers based on URL permissions

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

    @RequestMapping(value = "/enter", method = RequestMethod.GET)
    public ResultMap login() {
        return resultMap.success().message("Welcome in. You are a tourist.");
    }

    @RequestMapping(value = "/getMessage", method = RequestMethod.GET)
    public ResultMap submitLogin() {
        return resultMap.success().message("You have access to information about this interface!"); }}Copy the code
Common login user @restController@requestMapping ("/user")
public class UserController{
    @Autowired    
    private final ResultMap resultMap;

    @RequestMapping(value = "/getMessage", method = RequestMethod.GET)
    public ResultMap getMessage() {
        return resultMap.success().message("You have user rights to get information about this interface!"); }}Copy the code
Administrator @restController@requestMapping ("/admin")
public class AdminController {
    @Autowired    
    private final ResultMap resultMap;

    @RequestMapping(value = "/getMessage", method = RequestMethod.GET)
    public ResultMap getMessage() {
        return resultMap.success().message("You have administrator privileges and can get information about this interface!"); }}Copy the code

Notice that the CustomRealm class threw an AccountException exception. Now create a class to catch the exception

@RestControllerAdvice public class ExceptionController { private final ResultMap resultMap; @Autowired public ExceptionController(ResultMap resultMap) { this.resultMap = resultMap; @ExceptionHandler(AccountException.class) public ResultMap handleShiroException(Exception ex) {returnresultMap.fail().message(ex.getMessage()); }}Copy the code

And of course there’s the LoginController that does things like log in

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

    @RequestMapping(value = "/notLogin", method = RequestMethod.GET)
    public ResultMap notLogin() {
        return resultMap.success().message("You haven't logged in yet!");
    }

    @RequestMapping(value = "/notRole", method = RequestMethod.GET)
    public ResultMap notRole() {
        return resultMap.success().message("You have no permission!");
    }

    @RequestMapping(value = "/logout", method = RequestMethod.GET)
    public ResultMap logout() { Subject subject = SecurityUtils.getSubject(); / / cancellation of the subject. Logout ();return resultMap.success().message("Logout successful!"); } /** * login ** @param username username * @param password password */ @requestmapping (value ="/login", method = RequestMethod.POST) public ResultMap login(String username, String password) {// Create a subject from SecurityUtils subject subject = securityutils.getSubject (); // Prepare token (token) before authentication submission UsernamePasswordToken Token = new UsernamePasswordToken(username, password); // Execute authentication login subject. Login (token); String role = usermapper. getRole(username);if ("user".equals(role)) {
            return resultMap.success().message("Welcome aboard.");
        }
        if ("admin".equals(role)) {
            return resultMap.success().message("Welcome to the admin page.");
        } 
        return resultMap.fail().message("Permission error!"); }}Copy the code

test