preface
In enterprise project development, it is often necessary to control the Security and permission of the system. The common Security frameworks include Spring Security, Apache Shiro and so on. This article focuses on a brief introduction to Spring Security, followed by a simple example through Spring Boot integration.
Spring Security
What is Spring Security?
Spring Security is a Security framework based on Spring AOP and Servlet Filter that provides a comprehensive Security solution that provides user authentication and permission control at the Web request and method invocation levels.
The security of Web applications usually includes Authentication and Authorization.
User authentication verifies whether a user is a legitimate user of the system, that is, whether a user can access the system. User authentication generally requires a user name and password. The system verifies the user name and password to complete the authentication.
User authorization refers to verifying whether a user has permission to perform an operation.
Principle 2.
Spring Security features are implemented through a series of filter chains that work together. Here is the default security filter chain printed at project startup (Integration 5.2.0) :
[
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@5054e546,
org.springframework.security.web.context.SecurityContextPersistenceFilter@7b0c69a6,
org.springframework.security.web.header.HeaderWriterFilter@4fefa770,
org.springframework.security.web.csrf.CsrfFilter@6346aba8,
org.springframework.security.web.authentication.logout.LogoutFilter@677ac054,
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@51430781,
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@4203d678,
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@625e20e6,
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@19628fc2,
org.springframework.security.web.session.SessionManagementFilter@471f8a70,
org.springframework.security.web.access.ExceptionTranslationFilter@3e1eb569,
org.springframework.security.web.access.intercept.FilterSecurityInterceptor@3089ab62
]
Copy the code
- WebAsyncManagerIntegrationFilter
- SecurityContextPersistenceFilter
- HeaderWriterFilter
- CsrfFilter
- LogoutFilter
- UsernamePasswordAuthenticationFilter
- RequestCacheAwareFilter
- SecurityContextHolderAwareRequestFilter
- AnonymousAuthenticationFilter
- SessionManagementFilter
- ExceptionTranslationFilter
- FilterSecurityInterceptor
Detailed interpretation can refer to: blog.csdn.net/dushiwodecu…
3. Core components
SecurityContextHolder
Used to store the details of the application security Context, such as the user object information of the current operation, authentication status, and role permission information. By default, the SecurityContextHolder uses ThreadLocal to store this information, meaning that the security context is always available for methods in the same thread of execution.
Gets information about the current user
Because identity information is tied to the thread, user information can be obtained using static methods anywhere in the program. For example, to get the name of the currently authenticated user, the code looks like this:
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}
Copy the code
GetAuthentication () returns authentication information, getPrincipal() returns identity information, and UserDetails is the encapsulation class for user information.
Authentication
Authentication information interface, integrated with the Principal class. The method in this interface is as follows:
Interface methods | Functional specifications |
---|---|
getAuthorities() | Gets the list of permission information. The default isGrantedAuthority Interface implementation classes, typically a series of strings representing permission information |
getCredentials() | Obtain the password certificate submitted by the user. The password characters entered by the user are removed after authentication to ensure security |
getDetails() | Obtain user details, which record the equivalent IP address, sessionID, and certificate serial number |
getPrincipal() | Retrieve user identity information, and in most cases returnUserDetails The implementation class of an interface, one of the most commonly used interfaces in a framework |
AuthenticationManager
Authentication manager, responsible for validation. After successful Authentication, the AuthenticationManager returns an Authentication instance filled with the user’s Authentication information (including permission information, identity information, details, etc., but the password is usually removed). Authentication is then set into the SecurityContextHolder.
The AuthenticationManager interface is the core interface related to authentication and the entry for initiating authentication. However, it usually does not directly authenticate. Its common implementation class ProviderManager maintains an internal List of List
that stores various authentication methods. By default, You only need to authenticate with an AuthenticationProvider to be considered as a successful login.
UserDetailsService
Responsible for loading user information from a specific place, usually via JdbcDaoImpl loading from a database, or memory-mapped InMemoryDaoImpl.
UserDetails
This interface represents the most detailed user information. The method in this interface is as follows:
Interface methods | Functional specifications |
---|---|
getAuthorities() | Gets the permissions granted to the user |
getPassword() | Get the user’s correct password, which will be combined during authenticationAuthentication In thegetCredentials() Do than |
getUsername() | Gets the user name used for authentication |
isAccountNonExpired() | Indicates whether the user’s account has expired. Expired users cannot be verified |
isAccountNonLocked() | Indicates whether the user’s account is locked. Locked user cannot be verified |
isCredentialsNonExpired() | Indicates whether the user’s credentials (password) have expired. Users with expired credentials cannot be verified |
isEnabled() | Indicates whether a user is enabled. Disabled users cannot be verified |
Spring Security of actual combat
1. System design
This paper mainly uses Spring Security to realize the permission control and Security authentication of the system page. This example does not do detailed data increase, delete, change and check. SQL can be downloaded in the complete code, mainly based on the database to do the permission control of the page and Ajax requests.
1.1 technology stack
- Programming language: Java
- Programming framework: Spring, Spring MVC, Spring Boot
- ORM framework: MyBatis
- View template engine: Thymeleaf
- Security Framework: Spring Security (5.2.0)
- Database: MySQL
- Front end: Layui, JQuery
1.2 Functional Design
- Log in and log out
- Realizes the permission control of menu URL jump
- Implement permission control for button Ajax requests
- Prevents cross-site request forgery (CSRF) attacks
1.3 Database layer design
T_user user table
field | type | The length of the | Whether is empty | instructions |
---|---|---|---|---|
id | int | 8 | no | Primary key, self-growing |
username | varchar | 20 | no | The user name |
password | varchar | 255 | no | password |
T_role role table
field | type | The length of the | Whether is empty | instructions |
---|---|---|---|---|
id | int | 8 | no | Primary key, self-growing |
role_name | varchar | 20 | no | Character name |
T_menu menu table
field | type | The length of the | Whether is empty | instructions |
---|---|---|---|---|
id | int | 8 | no | Primary key, self-growing |
menu_name | varchar | 20 | no | The name of the menu |
menu_url | varchar | 50 | is | Menu URL (Controller request path) |
T_user_roles User permission table
field | type | The length of the | Whether is empty | instructions |
---|---|---|---|---|
id | int | 8 | no | Primary key, self-growing |
user_id | int | 8 | no | The users table id |
role_id | int | 8 | no | Role table id |
T_role_menus Permission menu table
field | type | The length of the | Whether is empty | instructions |
---|---|---|---|---|
id | int | 8 | no | Primary key, self-growing |
role_id | int | 8 | no | Role table id |
menu_id | int | 8 | no | Menu table id |
Entity classes are not listed here in detail.
2. Code implementation
2.0 Dependencies
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <! > <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> <! -- This needs to betrueHot deployment only works --> </dependency> <! --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <! -- mybaits --> <dependency> <groupId>org.mybatis.spring.boot</groupId> < artifactId > mybatis - spring - the boot - starter < / artifactId > < version > 2.1.0 < / version > < / dependency > <! -- thymeleaf --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <! -- alibaba fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> The < version > 1.2.47 < / version > < / dependency > <! -- spring security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> </dependencies>Copy the code
2.1 inheritance WebSecurityConfigurerAdapter custom Spring Security configuration
/** prePostEnabled: Determines whether PreAuthorize, @postauthorize,.. Jsr250Enabled: Determines the JSR-250 Annotations [@rolesallowed..] Is available. * / @ Configurable @ EnableWebSecurity / / open Spring Security method-level Security annotations @ EnableGlobalMethodSecurity @EnableGlobalMethodSecurity(prePostEnabled =true,securedEnabled = true,jsr250Enabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter{ @Autowired private CustomAccessDeniedHandler customAccessDeniedHandler; @Autowired private UserDetailsService userDetailsService; Public void configure(WebSecurity WebSecurity) {public void configure(WebSecurity WebSecurity) webSecurity.ignoring().antMatchers("/"."/css/**"."/js/**"."/images/**"."/layui/**"); } /** * HTTP request setting */ @override public void configure(HttpSecurity HTTP) throws Exception {//http.csrf().disable(); Http.headers ().frameoptions ().disable(); / / to solvein a frame because it set 'X-Frame-Options' to 'DENY'Problem / / HTTP. Anonymous (). The disable (); http.authorizeRequests() .antMatchers("/login/**"."/initUserData")// do not block login-related methods. PermitAll () //.antmatchers ()"/user").hasRole("ADMIN"// anyRequest() //.authenticated()// Any unmatched URL only needs to authenticate users to access.anyRequest().access()"@rbacPermission.hasPermission(request, authentication)".and().formlogin ().loginPage()"/")
.loginPage("/login"// Login request page.loginprocessingURL ("/login"// Login POST request path.usernameParameter ("username"// Login username parameter.passwordparameter ("password") // Login password parameter. DefaultSuccessUrl ("/main") / / default login success page) and (). ExceptionHandling () accessDeniedHandler (customAccessDeniedHandler) / / no permissions processor. And (). The logout () .logoutSuccessUrl("/login? logout"); / / log out successful URL} / * * * custom user information interface * / @ Override public void the configure (AuthenticationManagerBuilder auth) throws the Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } /** * Password encryption algorithm * @return
*/
@Bean
public BCryptPasswordEncoder passwordEncoder() {
returnnew BCryptPasswordEncoder(); }}Copy the code
2.2 User-defined implementation of UserDetails interface, extended attributes
public class UserEntity implements UserDetails { /** * */ private static final long serialVersionUID = -9005214545793249372L; private Long id; // User id private String username; // Username private String password; // password private List<Role> userRoles; // private List<Menu> roleMenus; Private Collection<? extends GrantedAuthority> authorities; publicUserEntity() {
}
public UserEntity(String username, String password, Collection<? extends GrantedAuthority> authorities,
List<Menu> roleMenus) {
this.username = username;
this.password = password;
this.authorities = authorities;
this.roleMenus = roleMenus;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public List<Role> getUserRoles() {
return userRoles;
}
public void setUserRoles(List<Role> userRoles) {
this.userRoles = userRoles;
}
public List<Menu> getRoleMenus() {
return roleMenus;
}
public void setRoleMenus(List<Menu> roleMenus) {
this.roleMenus = roleMenus;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true; }}Copy the code
2.3 Customizing the UserDetailsService Interface
* @author Charlie ** / @service public class UserDetailServiceImpl implements UserDetailsService { private Loggerlog= LoggerFactory.getLogger(UserDetailServiceImpl.class); @Autowired private UserDao userDao; @Autowired private RoleDao roleDao; @Autowired private MenuDao menuDao; @ Override public UserEntity loadUserByUsername (String username) throws UsernameNotFoundException {/ / according to the user name for the user UserEntity user = userDao.getUserByUsername(username); System.out.println(user);if(user ! = null) { System.out.println("UserDetailsService"); / / retrieve the user roles according to the user id List < Role > roles. = roleDao getUserRoleByUserId (user) getId ()); Collection<SimpleGrantedAuthority> authorities = new HashSet<SimpleGrantedAuthority>();for(Role role : roles) { authorities.add(new SimpleGrantedAuthority(role.getRoleName())); } / / filling permissions Menu List < Menu > menus. = menuDao getRoleMenuByRoles (roles);return new UserEntity(username,user.getPassword(),authorities,menus);
} else {
System.out.println(username +" not found");
throw new UsernameNotFoundException(username +" not found"); }}}Copy the code
2.4 Customization Implements URL permission control
/** * RBAC data model control permission * @author Charlie ** / @component ("rbacPermission")
public class RbacPermission{
private AntPathMatcher antPathMatcher = new AntPathMatcher();
public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
Object principal = authentication.getPrincipal();
boolean hasPermission = false;
ifList<Menu> Menus = ((UserEntity).getrolemenus (); System.out.println(menus.size());for (Menu menu : menus) {
if (antPathMatcher.match(menu.getMenuUrl(), request.getRequestURI())) {
hasPermission = true;
break; }}}returnhasPermission; }}Copy the code
2.5 implementation AccessDeniedHandler
Custom processing of unauthorized requests
/ * * * process has no right to request * * * / @ @ author Charlie Component public class CustomAccessDeniedHandler implements AccessDeniedHandler { private Loggerlog = LoggerFactory.getLogger(CustomAccessDeniedHandler.class);
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
boolean isAjax = ControllerTools.isAjaxRequest(request);
System.out.println("CustomAccessDeniedHandler handle");
if(! response.isCommitted()) {if (isAjax) {
String msg = accessDeniedException.getMessage();
log.info("accessDeniedException.message:" + msg);
String accessDenyMsg = "{\"code\":\"403\",\" MSG \":\" No permission \"}";
ControllerTools.print(response, accessDenyMsg);
} else {
request.setAttribute(WebAttributes.ACCESS_DENIED_403, accessDeniedException);
response.setStatus(HttpStatus.FORBIDDEN.value());
RequestDispatcher dispatcher = request.getRequestDispatcher("/ 403");
dispatcher.forward(request, response);
}
}
}
public static class ControllerTools {
public static boolean isAjaxRequest(HttpServletRequest request) {
return "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
}
public static void print(HttpServletResponse response, String msg) throws IOException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8"); PrintWriter writer = response.getWriter(); writer.write(msg); writer.flush(); writer.close(); }}}Copy the code
2.6 relevant Controller
Login/logout jump
@author Charlie ** / @controller public class LoginController {@getMapping ("/login")
public ModelAndView login(@RequestParam(value = "error", required = false) String error,
@RequestParam(value = "logout", required = false) String logout) {
ModelAndView mav = new ModelAndView();
if(error ! = null) { mav.addObject("error"."Incorrect username or password");
}
if (logout! = null) { mav.addObject("msg"."Exit successful");
}
mav.setViewName("login");
returnmav; }}Copy the code
Successful login
@Controller
public class MainController {
@GetMapping("/main")
public ModelAndView toMainPage() {/ / get the login user name Object. Principal = SecurityContextHolder getContext () getAuthentication () getPrincipal (); String username=null;if(principal instanceof UserDetails) {
username=((UserDetails)principal).getUsername();
}else {
username=principal.toString();
}
ModelAndView mav = new ModelAndView();
mav.setViewName("main");
mav.addObject("username", username);
returnmav; }}Copy the code
Used for testing page access with different permissions
@author Charlie ** / @controller public class ResourceController {@getMapping ("/publicResource")
public String toPublicResource() {
return "resource/public";
}
@GetMapping("/vipResource")
public String toVipResource() {
return "resource/vip"; }}Copy the code
For ajax request testing with different permissions
@author Charlie ** / @restController @requestMapping ("/test")
public class HttptestController {
@PostMapping("/public")
public JSONObject doPublicHandler(Long id) {
JSONObject json = new JSONObject();
json.put("code", 200);
json.put("msg"."Request successful" + id);
return json;
}
@PostMapping("/vip")
public JSONObject doVipHandler(Long id) {
JSONObject json = new JSONObject();
json.put("code", 200);
json.put("msg"."Request successful" + id);
returnjson; }}Copy the code
2.7 Related HTML pages
The login page
<form class="layui-form" action="/login" method="post">
<div class="layui-input-inline">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
<input type="text" name="username" required
placeholder="Username" autocomplete="off" class="layui-input">
</div>
<div class="layui-input-inline">
<input type="password" name="password" required placeholder="Password" autocomplete="off"
class="layui-input">
</div>
<div class="layui-input-inline login-btn">
<button id="btnLogin" lay-submit lay-filter="*" class="layui-btn"</button> </div> <div class="form-message">
<label th:text="${error}"></label>
<label th:text="${msg}"></label>
</div>
</form>
Copy the code
Prevents cross-site request forgery (CSRF) attacks
Log out
<form id="logoutForm" action="/logout" method="post"
style="display: none;">
<input type="hidden" th:name="${_csrf.parameterName}"
th:value="${_csrf.token}">
</form>
<a
href="javascript:document.getElementById('logoutForm').submit();"> Exit the system </a>Copy the code
Ajax request page
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" id="hidCSRF">
<button class="layui-btn" id="btnPublic"</button> <br> <br> <button class="layui-btn" id="btnVip"< p style = "max-width: 100%; clear: bothtype="text/javascript" th:src="@ {/ js/jquery - 1.8.3. Min. Js}"></script>
<script type="text/javascript" th:src="@{/layui/layui.js}"></script>
<script type="text/javascript">
layui.use('form'.function() {
var form = layui.form;
$("#btnPublic").click(function(){
$.ajax({
url:"/test/public".type:"POST",
data:{id:1},
beforeSend:function(xhr){
xhr.setRequestHeader('X-CSRF-TOKEN', $("#hidCSRF").val());
},
success:function(res){
alert(res.code+":"+res.msg); }}); }); $("#btnVip").click(function(){
$.ajax({
url:"/test/vip".type:"POST",
data:{id:2},
beforeSend:function(xhr){
xhr.setRequestHeader('X-CSRF-TOKEN', $("#hidCSRF").val());
},
success:function(res){
alert(res.code+":"+res.msg); }}); }); }); </script>Copy the code
2.8 test
The test provides two accounts: user and admin (the password is the same as the account)
As admin has set all the access permissions as the administrator, only the test results of user are shown here.
The complete code
github
Yards cloud
The copyright of this article belongs to Chaowu And Qinghan, please indicate the source.
Spring Boot 2.X(18) : Integrated Spring Security- Login authentication and permission control
The original address: https://www.zwqh.top/article/info/27
If the article is not enough, welcome to point, the follow-up will be improved.
If the article is helpful to you, please give me a like, please scan the code to pay attention to my public number, the article continues to update…