This is the fourth day of my participation in the November Gwen Challenge. Check out the details: The last Gwen Challenge 2021

Shiro profile

Apache Shiro is a powerful and easy-to-use Java security framework that allows you to do: authentication, authorization, encryption, session management, integration with the Web, caching, and more. These are not too hi, a person can liver so many things, can be called the king of liver. Shiro is also a project under Apache, which gives it a solid footing and is not tied to any framework or container.

Integrate shiro steps

Let’s start by looking at how to integrate Shiro.

Here need to remind my working experience of the few small driver, meet don’t understand the technology do not scratch the scalp reading concept, hands-on implementation code again, sometimes until the code to run again, watching the results after come back, to see the concept, le le code implementation, will have different feelings, if still feel difficult in study, can come from in the comments section, There are many gods in the community who can solve the problem.

  • Configure the core security transaction manager
@Bean public SecurityManager securityManager() { /** 1. */ **loginRealm for login authentication CustomRealm certification for other interface * / securityManager. SetRealms (Lists. NewArrayList (customRealm (), loginRealm ())); DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); / * * 2. Set the authentication strategy. * / securityManager setAuthenticator (authenticator ()); /** 3. Close shiro's built-in session. Make every request filtered */ DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO(); subjectDAO.setSessionStorageEvaluator(sessionStorageEvaluator()); securityManager.setSubjectDAO(subjectDAO); return securityManager; }Copy the code

We’ve introduced two types of authentication (loginRealm and customRealm) to create a new Realm.

In simple terms, the background will receive requests from the various scenarios, such as login, new user registration, and so on, we can’t slam the door, all with a policy to check all the requests, such as the login request need to invoke a database query login information is correct, and once after the success of the login, send again other interface request, It is not appropriate to connect to the database every time to check whether the user login information is correct. Therefore, other policies are required.

Therefore, we use two realms, the loginRealm that handles login authentication. This Realm handles only authenticated login requests to verify that the login is correct. All other interface requests are intercepted by customRealm.

Enticator () : setAuthenticator() : setAuthenticator() : setAuthenticator() : setAuthenticator() : setAuthenticator() : setAuthenticator()

/** * If there are multiple realms, Set authentication strategy * / @ Bean public ModularRealmAuthenticator authenticator () {ModularRealmAuthenticator authenticator = new MultiRealmAuthenticator(); / / multiple Realm authentication strategy, the default AtLeastOneSuccessfulStrategy AuthenticationStrategy strategy = new FirstSuccessfulStrategy (); authenticator.setAuthenticationStrategy(strategy); return authenticator; }Copy the code

Is not a face meng force, don’t worry, here is a specific scene: By default, each request to our interface is verified by both realms. If one of these realms succeeds and the other fails, then the request succeeds or fails.

  • FirstSuccessfulStrategy: Return only the first Realm authenticated successfully if one Realm has been authenticated successfully. (Note: “first” refers to the realm successfully authenticated)
  • AtLeastOneSuccessfulStrategy: as long as there is a Realm authentication is successful, unlike FirstSuccessfulSTRATEGY, will return all Realm authentication is successful authentication information;
  • AllSuccessfulStrategy: All Realm authentications are successful and return authentication information for all Realm authentications, failing if one fails.

Finally, in step 3, we disable the session, do not save the user login status, and ensure that each request is re-authenticated.

/** * Disable session and do not save user login status. Guarantee that every request to certification * / @ Bean protected SessionStorageEvaluator SessionStorageEvaluator () {DefaultSessionStorageEvaluator sessionStorageEvaluator = new DefaultSessionStorageEvaluator(); sessionStorageEvaluator.setSessionStorageEnabled(false); return sessionStorageEvaluator; }Copy the code
  • Custom LoginRealm

LoginRealm only processes login requests. Its implementation logic is to retrieve the account password from the request interface, query it through the database, and save the query results to Redis for verification on subsequent interface requests.

/** * query database, Return (pseudocode) */ @override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { LoginToken token = (LoginToken) authenticationToken; String username = token.getUsername(); String password = new String((char[]) authenticationToken.getCredentials()); / / query whether there is the current user, the effective user query results can only be 1 SysUser user. = loginService getUserByNameAndPwd (username, password); If (user == null){throw new AuthException(login_pwd_error.getCode (), "login failed, username or password error "); } // Cache current login user information redisutil. setex(key, value); return new SimpleAuthenticationInfo(jwtEntity, password, getName()); }Copy the code
  • Custom CustomRealm

LoginReaml will cache the LoginReaml user information, and then each time the interface calls the background, it will carry the token field in the header for authentication, and the authentication will be conducted in the CustomRealm.

@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //1. Obtain request Token CustomerToken jwtToken = (CustomerToken) authenticationToken; . Omit //2. Parse the username and password from JWT. String username = ""; String jwtCache = redisutil. get(stringutils. joinWith(SYMBOL_UNDERLINE, REDIS_KEY_PREFIX_LOGIN_ACCOUNT, username), 0); If (jwtCache == null) {log.warn("[jwtCache failed, query database account = {}]", username); } else {log.error(" account = {}]", username); } else if (! Stringutils.equals (password, cachejwtobj.getPassword ())) {log.error(" Cache password failed "); } } return new SimpleAuthenticationInfo(jwtObject, jwtToken.getCredentials(), getName()); }Copy the code
  • Configuring Interception

Some requests do not need to be blocked, such as registering interfaces and static resources (CSS, images, JS, etc.). In this case, we need to set shiro in advance.

ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); . / / login address shiroFilterFactoryBean setLoginUrl ("/login "); / / after the success of the login to jump connection shiroFilterFactoryBean setSuccessUrl ("/authorized "); / / unauthorized jump address shiroFilterFactoryBean setUnauthorizedUrl (" / 403 "); / / add custom filters (JwtFilter) Map < String, Filter > filterMap = Maps. NewHashMapWithExpectedSize (1); filterMap.put(JwtFilter.class.getName(), new JwtFilter()); shiroFilterFactoryBean.setFilters(filterMap); LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); // Set resource access permissions (anon means resources can be accessed anonymously). filterChainDefinitionMap.put("/css/**", "anon"); filterChainDefinitionMap.put("/js/**", "anon"); filterChainDefinitionMap.put("/login", "anon"); filterChainDefinitionMap.put("/**", JwtFilter.class.getName()); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);Copy the code
  • Log on to the test

LoginRealm authentication is triggered when the user name and password passed in by the front end are encapsulated in LoginToken and the subject-.login () method is called.

LoginToken loginToken = new LoginToken(username,password); Subject subject = SecurityUtils.getSubject(); // Login (to LoginRealm authentication) subject.login(usernamePasswordToken); // Return front-end token information JwtEntity JwtEntity = (JwtEntity) subject.getPrincipal(); return new SysUserLoginDto(JwtUtils.createJwtToken(JSON.toJSONString(jwtEntity)), jwtEntity.getLoginId());Copy the code

The token value generated by JWT can be obtained as a result of the test. In subsequent requests, the token value should be carried for authentication:

{"token":"eyJhbGciOiJIUzI1NiJ9.eyJqd3RLZXkiOiJ7XCJhY2NvdW50XCI6XCIxXCIsXCJsb2dpbklkXCI6MSxcInBhc3N3b3JkXCI6XCIweEMwbWZJV GR4MjJ3ejFKMVU1c3pnPT1cIixcInNlc3Npb25JZFwiOlwiODg2MjcyNDkyOTE0NjA2MTJcIn0iLCJleHAiOjE2MjY5MjI5MDJ9.VF8R9X3hZb6SDvShZbRd SRgwaAUUE7dC7XQhIuWBSn4","loginId":1}Copy the code