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




user

role

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); / / setLoginUrl if not set value, the default will automatically find under the root directory of the Web project "/ login JSP 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 / / the main 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 "); return shiroFilterFactoryBean; } / injection securityManager * / * * * @ Bean public securityManager securityManager () {DefaultWebSecurityManager securityManager  = new DefaultWebSecurityManager(); // Set realm.securityManager.setrealm (customRealm()); return securityManager; } /** * Custom authentication realm; * <p> * You must write this class with the @bean annotation to inject CustomRealm, Public CustomRealm CustomRealm() {return new 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




DefaultFilter

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] If the requested URL port is not 8081, jump toschemal://serverName:8081? queryStringWhere 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, right? 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 User identity token * @return Returns an AuthenticationInfo instance that encapsulates user information */ @Override protected AuthenticationInfo doGetAuthenticationInfo(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 user name "); } else if (! Password.equals (new String((char[]) token.getCredentials()))) {throw new AccountException(" password incorrect "); } return new SimpleAuthenticationInfo(token.getPrincipal(), password, getName()); } /** * Get 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 info.setroles (set); return info; }}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

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 to enter, your identity is a tourist "); } @RequestMapping(value = "/getMessage", Method = requestMethod.get) public ResultMap submitLogin() {return resultmap.success ().message(" You have permission to obtain information about this interface!" ); }}Copy the code
@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 permission to obtain this interface information!" ); }}Copy the code
Admin@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 permission to obtain this interface information!" ); }}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) { return resultMap.fail().message(ex.getMessage()); }}Copy the code

There’s a 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 have not logged in yet!" ); } @RequestMapping(value = "/notRole", Method = requestmethod.get) public ResultMap notRole() {return resultmap.success ().message(" You do not have permissions!" ); } @RequestMapping(value = "/logout", method = RequestMethod.GET) public ResultMap logout() { Subject subject = SecurityUtils.getSubject(); / / cancellation of the subject. Logout (); Return resultmap.success ().message(" Successful logout! ") ); } @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 to login "); } if ("admin".equals(role)) {return resultmap.success ().message(" welcome to admin page "); } return resultmap.fail ().message(" Permission error!" ); }}Copy the code

test


Access information interface before login

Common user login

Password mistake

Administrator login

Access to information

Access to information

The cancellation