preface
During this period of time, I chose Shiro among learning Springboot, Spring Security and Shiro. The reason is that Shiro has low learning cost and may not be as powerful as Spring Security, but it may not need so complicated things in practical work. Coarse-grained can also be customized as needed, so using Shiro as small and simple as possible is sufficient. This article mainly refers to the Z77Z SpringBoot+ Shiro integration learning login authentication and permission control source code project address
About Shiro
Shiro’s core:
- Subject: Records the current operation user. Subject is an interface in Shiro, and many authentication methods are defined in the interface. External programs are authenticated and authorized by Subject, while Subject is authenticated and authorized by SecurityManager
- SecurityManager: Manages the Subject, which is the core of Shiro. SecurityManager is an interface that inherits the Authenticator, Authorizer, and SessionManager interfaces.
- Authenticator: Authenticates the user identity
- Authorizer: Determines when a user has the permission after being authenticated
- Realm: Obtain user permission data
- SessionManager (Session Management) : Shiro framework defines a set of session management, which does not rely on the Web container session, so Shiro can be used in non-Web applications, or distributed applications can be managed at a single point, this feature enables single sign-on.
- CacheManager(CacheManager) : stores user permission data in the cache to improve performance.
Learning goals
If the login authentication fails, the system returns to the login page. You can access the specified link only after the authentication is successful and you have the permission. Otherwise, the system switches to page 403.
Add the dependent
<! -- shiro--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> The < version > 1.4.0 < / version > < / dependency >Copy the code
Add shiro configuration
Configuration also added a custom form interceptor MyFormAuthenticationFilter, to deal with abnormal login information and by return
ShiroConfig.java
/** * @author wgc * @date 2018/02/09 */ @Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); // Interceptor. Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>(); / / configuration will not be intercepted the link order of judgment filterChainDefinitionMap. Put ("/assets/**"."anon");
filterChainDefinitionMap.put("/css/**"."anon");
filterChainDefinitionMap.put("/js/**"."anon");
filterChainDefinitionMap.put("/img/**"."anon");
filterChainDefinitionMap.put("/layui/**"."anon");
filterChainDefinitionMap.put("/captcha/**"."anon");
filterChainDefinitionMap.put("/favicon.ico"."anon"); / / configuration exit filter, of which the specific exit code Shiro has achieved filterChainDefinitionMap for us. The put ("/logout"."logout"); / / <! -- Filter chain definition, executed from top to bottom, usually with /** at the bottom --> this is a pit, if not careful code will not work; // authc: All urls must be authenticated to be accessible; Anon: All urls can be accessed anonymously; / / user: certification by state or to remember the login (remeberMe) will be filterChainDefinitionMap. To put ("/ * *"."authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); // If this parameter is not set, the system automatically searches for those in the root directory of the Web project"/login.jsp"Page shiroFilterFactoryBean. SetLoginUrl ("/login"); / / to jump after a successful login link shiroFilterFactoryBean setSuccessUrl ("/index"); // Unauthorised interface; shiroFilterFactoryBean.setUnauthorizedUrl("/ 403"); / / custom interceptors Map < String, Filter > filters. = shiroFilterFactoryBean getFilters (); filters.put("authc", new MyFormAuthenticationFilter());
returnshiroFilterFactoryBean; } /** * authentication realm; (This needs to write by yourself, account password verification; Permissions, etc.) * @return myShiroRealm
*/
@Bean
public MyShiroRealm myShiroRealm(){
MyShiroRealm myShiroRealm = new MyShiroRealm();
returnmyShiroRealm; } /** * Security manager * @return securityManager
*/
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
returnsecurityManager; }}Copy the code
Realm implementations
Shiro obtains security data (such as users, roles, and permissions) from realms. To authenticate users, The SecurityManager needs to obtain the corresponding users from realms for comparison to determine whether they are valid. You also need to get the user’s role/permissions from Realm to verify that the user can operate. You can think of a Realm as a DataSource. Realm has two main methods:
- DoGetAuthorizationInfo (Get authorization information)
- DoGetAuthenticationInfo (get authentication related information) :
Custom Realm implementation
/** * @author wgc * @date 2018/02/09 */ public class MyShiroRealm extends AuthorizingRealm { private static final Logger logger = LoggerFactory.getLogger(MyShiroRealm.class); @Resource private ShiroService userInfoService; // Override protected AuthorizationInfodoGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
UserInfo userInfo = (UserInfo)principals.getPrimaryPrincipal();
for(SysRole sysRole : userInfoService.findSysRoleListByUsername(userInfo.getUsername())){
authorizationInfo.addRole(sysRole.getRolename());
logger.info(sysRole.toString());
for(SysPermission sysPermission : userInfoService.findSysPermissionListByRoleId(sysRole.getId())){ logger.info(sysPermission.toString()); authorizationInfo.addStringPermission(sysPermission.getUrl()); }};returnauthorizationInfo; } // It is mainly used for authentication, that is, to verify that the user entered the correct account and password. @Override protected AuthenticationInfodoGetAuthenticationInfo(AuthenticationToken Token) throws AuthenticationException {// Obtains the user's entered account. String username = (String)token.getPrincipal(); logger.info("Login authentication for user [{}].. Verification begins",username); // Shiro can cache the User object according to the actual situation, if not, Shiro also has its own time interval mechanism. 2 minutes will not repeat this method the UserInfo the UserInfo. = userInfoService selectUserInfoByUsername (username);if(userInfo == null){throw new UnknownAccountException(); }returnnew SimpleAuthenticationInfo(userInfo, userInfo.getPassword(), getName()); }}Copy the code
Custom form validation
Here, we login by post Form on the login page. Since shiro has built-in FormAuthenticationFilter for authentication based on Form forms, shiro’s default filter will be called if it is not customized. Because we need to return a login failure message, we need to inherit a custom form filter, override the setFailureAttribute method (which is called when a login fails), and write the login failure message to the “shiroLoginFailure” property of the request. Print out from the front page.
Form authentication filter implementation
/ * * * * @ custom form certification author WGC * / public class MyFormAuthenticationFilter extends FormAuthenticationFilter {private static final Logger logger = LoggerFactory.getLogger(MyFormAuthenticationFilter.class); /** * Override the method to return the login information */ @override protected voidsetFailureAttribute(ServletRequest request, AuthenticationException ae) {
String className = ae.getClass().getName();
String message;
String userName = getUsername(request);
if (UnknownAccountException.class.getName().equals(className)) {
logger.info("Login authentication for user [{}].. Failed verification, unknown account", userName);
message = "Account does not exist";
} else if (IncorrectCredentialsException.class.getName().equals(className)) {
logger.info("Login authentication for user [{}].. Failed verification, wrong credentials.", userName);
message = "Incorrect password";
} else if(LockedAccountException.class.getName().equals(className)) {
logger.info("Login authentication for user [{}].. Authentication failed, account locked", userName);
message = "Account locked.";
} else if(ExcessiveAttemptsException.class.getName().equals(className)) {
logger.info("Login authentication for user [{}].. Failed verification, too many errors.", userName);
message = "Too many incorrect user names or passwords. Please try again ten minutes later.";
} else if(AuthenticationException. Class. GetName () equals (className)) {/ / by processing Shiro's runtime AuthenticationException can control the user failed to login or password mistake logger.info("Login authentication for user [{}].. Validation failed, unknown error", userName);
message = "Incorrect username or password";
} else{ message = className; } request.setAttribute(getFailureKeyAttribute(), message); }}Copy the code
Web related
The login page
controller
@RequestMapping("/login")
public String loginForm() {
return "login";
}
Copy the code
login.html
<! DOCTYPE html> <html lang="en" class="no-js" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8"/> <title> Login -- Layui admin template </title> <link rel="stylesheet" href=".. /.. /layui/css/layui.css" media="all" />
<link rel="stylesheet" href=".. /css/login.css" media="all" />
</head>
<body>
<div class="login"> <h1>layuiCMS- Admin login </h1> <form class="layui-form" method="post">
<div class="layui-form-item">
<input class="layui-input" name="username" placeholder="Username" type="text" autocomplete="off"/>
</div>
<div class="layui-form-item">
<input class="layui-input" name="password" placeholder="Password" type="password" autocomplete="off"/>
</div>
<button class="layui-btn login_btn" lay-submit="" lay-filter="login"</button> </form> </div> <scripttype="text/javascript" src=".. /layui/layui.js"></script>
<script th:inline="javascript">
layui.use(['layer'].function(){
var layer = layui.layer;
var message = [[${shiroLoginFailure}]]? [[${shiroLoginFailure}]]:getUrlPara("shiroLoginFailure");
if(message) { layer.msg(message); }});function getUrlPara(name)
{
var url = document.location.toString();
var arrUrl = url.split("?"+name +"=");
var para = arrUrl[1];
if(para)
return decodeURI(para);
}
</script>
</body>
</html>
Copy the code
The main page
controller
@RequestMapping({"/"."/index"})
public String index(Model model) {
UserInfo user = (UserInfo)SecurityUtils.getSubject().getPrincipal();
model.addAttribute("user", user);
return "index";
}
Copy the code
index.html
<! DOCTYPE html> <html lang="en" class="no-js" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8"/ > < title > home page < / title > < / head > < body > < h3 th: text ="${user.username}">user</h3>
</body>
</html>
Copy the code
test
After the task is started, any link under locahost:8080 will be redirected to the login page if you do not login. After a successful login, the index page will be displayed. Click “exit” on the main page to log out and jump to the login page
After login, the add page is successfully accessed. Change the access permission of Add in shiro configuration file to
filterChainDefinitionMap.put("/add"."Perms [permission deletion]");
Copy the code
A 404 error is reported because 403 page was not written. These actions above, will trigger the authority authentication methods: MyShiroRealm. DoGetAuthorizationInfo (), will trigger a once per visit.