In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-02-28 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Internet Technology >
Share
Shulou(Shulou.com)05/31 Report--
Today, I would like to share with you how to achieve Springboot authentication and dynamic rights management related knowledge points, detailed content, clear logic, I believe that most people still know too much about this knowledge, so share this article for your reference, I hope you can learn something after reading this article, let's take a look at it.
Process Analysis of knowledge Point Supplementary Shiro caching
In the original project, because the cache was not configured, the database was queried every time the current principal had access to it. Since permission data is typical of data that reads more and writes less, we should support it by adding caches.
When we join the cache, shiro first queries the relevant data in the cache when doing authentication. If there is no data in the cache, then query the database and write the found data to the cache. Next time, you can get the data from the cache instead of from the database. This can improve the performance of our application.
Next, let's implement the cache management part of shiro.
Shiro session mechanism
Shiro provides complete enterprise session management capabilities, independent of the underlying container (such as web container tomcat), and can be used in both JavaSE and JavaEE environments. It provides session management, session event monitoring, session storage / persistence, container-independent clustering, expiration / expiration support, transparent support for Web, SSO single sign-on support and other features.
We will use Shiro's session management to take over the web session of our application and store the session information through Redis.
Consolidation step to add cache CacheManager
In Shiro, it provides the CacheManager class for cache management.
Use the default EhCache implementation of Shiro
In shiro, the EhCache cache framework is used by default. EhCache is a pure Java in-process caching framework, which is fast and lean.
Introduction of shiro-EhCache dependency org.apache.shiro shiro-ehcache 1.4.0
In the process of integrating Redis with SpringBoot, you should also pay attention to the problem of version matching, otherwise it is possible to report exceptions that the method has not found.
Add cache configuration private void enableCache (MySQLRealm realm) {/ / enable global cache configuration realm.setCachingEnabled (true); / / enable authentication cache configuration realm.setAuthenticationCachingEnabled (true); / / enable authorization cache configuration realm.setAuthorizationCachingEnabled (true); / / for ease of operation, we give the cache the name realm.setAuthenticationCacheName ("authcCache"); realm.setAuthorizationCacheName ("authzCache") / / implement realm.setCacheManager (new EhCacheManager ()) by injecting cache;}
Then call this method in getRealm.
Tip: in this implementation, only local caching is implemented. In other words, the cached data shares the memory of the same machine as the application. If there is an outage or unexpected power outage on the server, the cached data will no longer exist. Of course, you can also use the cacheManager.setCacheManagerConfigFile () method to give the cache more configuration.
Next we will cache our permission data through Redis
Use the Redis implementation to add dependencies on org.crazycake shiro-redis 3.1.0 org.apache.shiro shiro-core configuration redis
Add the relevant configuration of redis to application.yml
Spring: redis: host: 127.0.0.1 port: 6379 password: hewenping timeout: 3000 jedis: pool: min-idle: 5 max-active: 20 max-idle: 15
Modify ShiroConfig configuration class and add shiro-redis plug-in configuration
/ * * shiro configuration class * @ author bingfengdev@aliyun.com * @ version 1.0 * @ date 9:11 on 2020-10-6 * / @ Configurationpublic class ShiroConfig {private static final String CACHE_KEY = "shiro:cache:"; private static final String SESSION_KEY = "shiro:session:"; private static final int EXPIRE = 18000; @ Value ("${spring.redis.host}") private String host @ Value ("${spring.redis.port}") private int port; @ Value ("${spring.redis.timeout}") private int timeout; @ Value ("${spring.redis.password}") private String password; @ Value ("${spring.redis.jedis.pool.min-idle}") private int minIdle; @ Value ("${spring.redis.jedis.pool.max-idle}") private int maxIdle @ Value ("${spring.redis.jedis.pool.max-active}") private int maxActive; @ Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor (org.apache.shiro.mgt.SecurityManager securityManager) {AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor (); authorizationAttributeSourceAdvisor.setSecurityManager (securityManager); return authorizationAttributeSourceAdvisor } / * create ShiroFilter interceptor * @ return ShiroFilterFactoryBean * / @ Bean (name = "shiroFilterFactoryBean") public ShiroFilterFactoryBean getShiroFilterFactoryBean (DefaultWebSecurityManager securityManager) {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean (); shiroFilterFactoryBean.setSecurityManager (securityManager); / / configure not to intercept paths and intercept paths, HashMap map = new HashMap (5) Map.put ("/ authc/**", "anon"); map.put ("/ login.html", "anon"); map.put ("/ js/**", "anon"); map.put ("/ css/**", "anon"); map.put ("/ *", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap (map) / / override the default login url shiroFilterFactoryBean.setLoginUrl ("/ authc/unauthc"); return shiroFilterFactoryBean;} @ Bean public Realm getRealm () {/ / set the credential matcher and modify it to hash credential matcher HashedCredentialsMatcher myCredentialsMatcher = new HashedCredentialsMatcher (); / / set algorithm myCredentialsMatcher.setHashAlgorithmName ("md5"); / / number of hashes myCredentialsMatcher.setHashIterations (1024) MySQLRealm realm = new MySQLRealm (); realm.setCredentialsMatcher (myCredentialsMatcher); / / enable cache realm.setCachingEnabled (true); realm.setAuthenticationCachingEnabled (true); realm.setAuthorizationCachingEnabled (true); return realm } / * create a security manager under the shiro web application * @ return DefaultWebSecurityManager * / @ Bean public DefaultWebSecurityManager getSecurityManager (Realm realm) {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager (); securityManager.setRealm (realm); securityManager.setCacheManager (cacheManager ()); SecurityUtils.setSecurityManager (securityManager); return securityManager } / * configure Redis Manager * @ Attention using the shiro-redis open source plug-in * @ return * / @ Bean public RedisManager redisManager () {RedisManager redisManager = new RedisManager (); redisManager.setHost (host); redisManager.setPort (port); redisManager.setTimeout (timeout); redisManager.setPassword (password); JedisPoolConfig jedisPoolConfig = new JedisPoolConfig () JedisPoolConfig.setMaxTotal (maxIdle+maxActive); jedisPoolConfig.setMaxIdle (maxIdle); jedisPoolConfig.setMinIdle (minIdle); redisManager.setJedisPoolConfig (jedisPoolConfig); return redisManager;} @ Bean public RedisCacheManager cacheManager () {RedisCacheManager redisCacheManager = new RedisCacheManager (); redisCacheManager.setRedisManager (redisManager ()); redisCacheManager.setKeyPrefix (CACHE_KEY) / / shiro-redis requires that entity classes placed in session must have an id identity / / which is part of the key that makes up the data stored in redis ("username"); return redisCacheManager;}}
Modify the doGetAuthenticationInfo method in MySQLRealm to take the User object as a whole as the first parameter of SimpleAuthenticationInfo. Shiro-redis takes the id value from the first parameter based on the value of the principalIdFieldName attribute of RedisCacheManager as part of the key of the data in redis.
/ * param token * @ return * @ throws AuthenticationException * / @ Overrideprotected AuthenticationInfo doGetAuthenticationInfo (AuthenticationToken token) throws AuthenticationException {if (token==null) {return null;} String principal = (String) token.getPrincipal (); User user = userService.findByUsername (principal) SimpleAuthenticationInfo simpleAuthenticationInfo = new MyAuthcInfo (/ / because the shiro-redis plug-in needs to get id from this attribute as the key of redis / / all user instead of username user is passed here, / / credential information user.getPassword (), / / encryption salt new CurrentSalt (user.getSalt ()), getName ()) Return simpleAuthenticationInfo;}
And modify the doGetAuthorizationInfo method in MySQLRealm to get the primary identity information from the User object.
/ * Licensing * @ param principals * @ return * / @ Overrideprotected AuthorizationInfo doGetAuthorizationInfo (PrincipalCollection principals) {User user = (User) principals.getPrimaryPrincipal (); String username = user.getUsername (); List roleList = roleService.findByUsername (username); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo (); for (Role role: roleList) {authorizationInfo.addRole (role.getRoleName ());} List roleIdList = new ArrayList () For (Role role: roleList) {roleIdList.add (role.getRoleId ());} List resourceList = resourceService.findByRoleIds (roleIdList); for (Resource resource: resourceList) {authorizationInfo.addStringPermission (resource.getResourcePermissionTag ());} return authorizationInfo;} Custom Salt
Because the default SimpleByteSource in Shiro does not implement the serialization interface, the salt generated by ByteSource.Util.bytes () makes an error during serialization, so you need to customize the Salt class and implement the serialization interface. And pass the salt value in the custom Realm authentication method using new CurrentSalt (user.getSalt ()).
/ * * since ByteSource in shiro does not implement the serialization interface, an error will occur during caching * therefore, we need to implement this interface by customizing ByteSource * @ author dependent handle bingfengdev@aliyun.com * @ version 1.0 * @ date 16:17 on 2020-10-8 * / public class CurrentSalt extends SimpleByteSource implements Serializable {public CurrentSalt (String string) {super (string) } public CurrentSalt (byte [] bytes) {super (bytes);} public CurrentSalt (char [] chars) {super (chars);} public CurrentSalt (ByteSource source) {super (source);} public CurrentSalt (File file) {super (file);} public CurrentSalt (InputStream stream) {super (stream) }} add Shiro Custom session add Custom session ID Generator / * * SessionId Generator *
@ author Lai Jian laibingf_dev@outlook.com
*
@ date 15:19 on 2020-8-15
* / public class ShiroSessionIdGenerator implements SessionIdGenerator {/ * implement SessionId generation * @ param session * @ return * / @ Override public Serializable generateId (Session session) {Serializable sessionId = new JavaUuidSessionIdGenerator () .generateId (session); return String.format ("login_token_%s", sessionId);}} add custom session manager / *
@ author Lai Jian laibingf_dev@outlook.com
*
@ date 15:40 on 2020-8-15
* / public class ShiroSessionManager extends DefaultWebSessionManager {/ / define constant private static final String AUTHORIZATION = "Authorization"; private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request"; / / rewrite constructor public ShiroSessionManager () {super (); this.setDeleteInvalidSessions (true) } / * the rewriting method implements obtaining Token from the request header to facilitate the unification of the interface * * each time the request comes in, * Shiro will find the Value (Token) * @ param request * @ param response * @ return * / @ Override public Serializable getSessionId (ServletRequest request) corresponding to the key of Authorization from the request header. ServletResponse response) {String token = WebUtils.toHttp (request) .getHeader (AUTHORIZATION) / / if token exists in the request header, get token if (! StringUtils.isEmpty (token)) {request.setAttribute (ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE); request.setAttribute (ShiroHttpServletRequest.REFERENCED_SESSION_ID, token); request.setAttribute (ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE); return token from the request header } else {/ / Cookie acquisition method is disabled here return null;} configure custom session manager
Add a configuration to the session manager in ShiroConfig
/ * SessionID generator * * / @ Beanpublic ShiroSessionIdGenerator sessionIdGenerator () {return new ShiroSessionIdGenerator ();} / * configure RedisSessionDAO * / @ Beanpublic RedisSessionDAO redisSessionDAO () {RedisSessionDAO redisSessionDAO = new RedisSessionDAO (); redisSessionDAO.setRedisManager (redisManager ()); redisSessionDAO.setSessionIdGenerator (sessionIdGenerator ()); redisSessionDAO.setKeyPrefix (SESSION_KEY); redisSessionDAO.setExpire (EXPIRE); return redisSessionDAO } / * configure Session Manager * @ Author Sans * * / @ Beanpublic SessionManager sessionManager () {ShiroSessionManager shiroSessionManager = new ShiroSessionManager (); shiroSessionManager.setSessionDAO (redisSessionDAO ()); / / disable cookie shiroSessionManager.setSessionIdCookieEnabled (false); / / disable session id rewriting shiroSessionManager.setSessionIdUrlRewritingEnabled (false); return shiroSessionManager;}
In the latest version (1.6.0), the setSessionIdUrlRewritingEnabled (false) configuration of session Manager does not take effect, resulting in multiple redirects for direct access to protected resources without authentication. The bug is resolved by switching the shiro version to 1.5.0.
Originally, this article was supposed to be posted last night, but it took a long time for this reason, so it was only posted today.
Modify the doGetAuthenticationInfo authentication method of custom Realm
Before the authentication information is returned, we need to make a judgment: if the current user has logged in on the old device, we need to delete the session id on the old device and take it offline.
/ * param token * @ return * @ throws AuthenticationException * / @ Overrideprotected AuthenticationInfo doGetAuthenticationInfo (AuthenticationToken token) throws AuthenticationException {if (token==null) {return null;} String principal = (String) token.getPrincipal (); User user = userService.findByUsername (principal) SimpleAuthenticationInfo simpleAuthenticationInfo = new MyAuthcInfo (/ / because the shiro-redis plug-in needs to get id from this attribute as the key of redis / / all user instead of username user is passed here, / / credential information user.getPassword (), / / encryption salt new CurrentSalt (user.getSalt ()), getName ()) / / to clear the old session of the current subject is equivalent to logging in to the system on the new computer, pushing the session you previously logged in on the old computer out of ShiroUtils.deleteCache (user.getUsername (), true); return simpleAuthenticationInfo;} modifying the login interface
We store the session information in redis and return the session Id to the user in the form of token after the user has been authenticated. Users request protected resources with this token. According to the token information, we go to the redis to obtain the user's permission information, so as to do access control.
@ PostMapping ("/ login") public HashMap login (@ RequestBody LoginVO loginVO) throws AuthenticationException {boolean flags = authcService.login (loginVO); HashMap map = new HashMap (3); if (flags) {Serializable id = SecurityUtils.getSubject (). GetSession (). GetId (); map.put ("msg", "login successful"); map.put ("token", id); return map;} else {return null }} add global exception handling / * * shiro exception handling * @ author dependent handle bingfengdev@aliyun.com * @ version 1.0 * @ date 18:01 * / @ ControllerAdvice (basePackages = "pers.lbf.springbootshiro") public class AuthExceptionHandler {/ / = authentication exception = / @ ExceptionHandler (ExpiredCredentialsException.class) @ ResponseBody public String expiredCredentialsExceptionHandlerMethod (ExpiredCredentialsException e) {return "credential expired" } @ ExceptionHandler (IncorrectCredentialsException.class) @ ResponseBody public String incorrectCredentialsExceptionHandlerMethod (IncorrectCredentialsException e) {return "user name or password error";} @ ExceptionHandler (UnknownAccountException.class) @ ResponseBody public String unknownAccountExceptionHandlerMethod (IncorrectCredentialsException e) {return "user name or password error";} @ ExceptionHandler (LockedAccountException.class) @ ResponseBody public String lockedAccountExceptionHandlerMethod (IncorrectCredentialsException e) {return "account locked" } / / = Authorization exception = / / @ ExceptionHandler (UnauthorizedException.class) @ ResponseBody public String unauthorizedExceptionHandlerMethod (UnauthorizedException e) {return "not authorized! Please contact the administrator for authorization ";}}
In actual development, the returned results should be unified and the business error code should be given. This is beyond the scope of this article, if necessary, please consider according to your own system characteristics.
Conduct test certification
Login success
Incorrect user name or password
For security reasons, do not reveal whether it is an incorrect user name or password.
Access to protected resources
Access authorized resources after authentication
Access to resources without permission after authentication
Direct access without authentication
View redis
The three key values correspond to authentication information cache, authorization information cache and session information cache respectively.
These are all the contents of the article "how to achieve authentication and dynamic rights management in Springboot". Thank you for reading! I believe you will gain a lot after reading this article. The editor will update different knowledge for you every day. If you want to learn more knowledge, please pay attention to the industry information channel.
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.