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 isGrantedAuthorityInterface 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 returnUserDetailsThe 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 authenticationAuthenticationIn 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

  1. Log in and log out
  2. Realizes the permission control of menu URL jump
  3. Implement permission control for button Ajax requests
  4. 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…