Spring Web integrates Shiro
Apache Shiro is a powerful Java security framework that provides authentication, authorization, encryption, and more to protect your applications from malicious attacks. In this article, we will describe how to integrate Apache Shiro into a Spring web application.
1. Add dependencies
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.7.1</version>
</dependency>
2. Configure Shiro
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="myRealm"/>
</bean>
<bean id="myRealm" class="com.example.MyRealm">
<!-- 设置密码匹配器 -->
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="SHA-256"/>
<property name="hashIterations" value="1000"/>
</bean>
</property>
</bean>
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login"/>
<property name="successUrl" value="/home"/>
<property name="unauthorizedUrl" value="/unauthorized"/>
<property name="filterChainDefinitions">
<value>
/login = anon
/logout = logout
/static/** = anon
/** = authc
</value>
</property>
</bean>
In the configuration above, we defined one SecurityManager
and one Realm for handling authentication and authorization requests. We also define one ShiroFilterFactoryBean
for creating a ShiroFilter
Filter that will handle all requests.
We pass all requests through the authc filter, which means only authenticated users can access them. We also define some exceptions like /login
and /static/**
, these paths will not require authentication.
3. Create Realm
Next, we need to create a Realm to handle authentication and authorization requests. Here is an example Realm implementation:
public class MyRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String) principals.getPrimaryPrincipal();
// 从数据库中获取用户的角色和权限信息
Set<String> roles = userService.findRolesByUsername(username);
Set<String> permissions = userService.findPermissionsByUsername(username);
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(roles);
authorizationInfo.setStringPermissions(permissions);
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
String password = new String((char[]) token.getCredentials());
// 从数据库中获取用户信息
User user = userService.findByUsername(username);
// 如果用户不存在,则抛出异常
if (user == null) {
throw new UnknownAccountException("用户不存在");
}
// 验证用户密码是否正确
if (!password.equals(user.getPassword())) {
throw new IncorrectCredentialsException("用户名或密码错误");
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, password, getName());
return authenticationInfo;
}
In this Realm, we implement doGetAuthorizationInfo
and doGetAuthenticationInfo
two methods.
doGetAuthorizationInfo
Used to obtain the user's role and permission information, which can be obtained from the database or other data sources. In this example, we get the user's roles and permissions by calling a UserService.
doGetAuthenticationInfo
Used to authenticate the user. In this example, we fetch the user's information from the database and compare if the passwords match. If the passwords do not match, an exception is thrown.
Fourth, write the controller
Now that we have Shiro set up, we need to write some controllers to handle requests. Here is an example controller:
@Controller
public class HomeController {
@RequestMapping("/home")
public String home() {
return "home";
}
@RequestMapping("/login")
public String login() {
return "login";
}
@RequestMapping("/logout")
public String logout() {
SecurityUtils.getSubject().logout();
return "redirect:/login";
}
@RequestMapping("/unauthorized")
public String unauthorized() {
return "unauthorized";
}
}
In this example, we define a HomeController
, which contains some methods to handle requests. /home
method will return a home view, /login
method will return a login view, /logout
method will log out the user and redirect to the login view, /unauthorized
method will return a unauthorized
view.
5. Create a view
<html>
<body>
<h1>Login</h1>
<form method="post" action="/login">
<label for="username">Username:</label>
<input type="text" id="username" name="username"/><br/>
<label for="password">Password:</label>
<input type="password" id="password" name="password"/><br/>
<input type="submit" value="Login"/>
</form>
</body>
</html>
In this example we create a simple login form where the user enters their username and password and the form is submitted to the /login
route.
6. Configure Shiro
The final step is to configure Shiro in Spring. We need to create a ShiroConfig class for configuring Shiro and Spring integration. Here is an example ShiroConfig class:
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilter() {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager());
shiroFilter.setLoginUrl("/login");
shiroFilter.setSuccessUrl("/home");
shiroFilter.setUnauthorizedUrl("/unauthorized");
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/logout", "logout");
filterChainDefinitionMap.put("/**", "authc");
shiroFilter.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilter;
}
@Bean
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm());
return securityManager;
}
@Bean
public UserRealm realm() {
UserRealm realm = new UserRealm();
return realm;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager());
return advisor;
}
}
In this example, we define a ShiroFilterFactoryBean
and a DefaultWebSecurityManager
, which are used to configure the Shiro filter and security manager. We also define a UserRealm, which is used to obtain the user's role and permission information.
In ShiroFilterFactoryBean, we define a filter chain for mapping requests to corresponding filters. In this example, we define a /login path for anonymous access, a /logout path for logout, and an authc filter for authentication. We also define default URLs for successful logins, and URLs for unauthorized ones.
In DefaultWebSecurityManager
, we set what we defined UserRealm
.
Finally, we define a AuthorizationAttributeSourceAdvisor
, which enables annotation-based authorization. @RequiresRoles
This will allow us to restrict access using and @RequiresPermissions
annotations on controller methods .
7. Test
Now that we've finished integrating and configuring Shiro, we can start the application and test it. Access in your browser http://localhost:8080/login
, enter your username and password, and click the Login button. If the username and password are correct, you will be redirected to http://localhost:8080/home
. If the username or password is incorrect, you will receive an error message.
If you try to access /home
path, you will be redirected to http://localhost:8080/login
path because you are not yet authenticated. If you log out and try to access the /home path again, you will get an unauthorized error message.