SpringBoot:集成SpringSecurity
一、创建项目,添加模块:

二、创建实体类SysUser,该类必须继承SpringSecurity提供的UserDetails类:
package com.example.entity; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; public class SysUser implements UserDetails { private static final long serialVersionUID = -1533292207697155409L; private String id; private String username; private String password; private Integer status; private List<SysRole> sysRoles; private List<SysPrivilegeMenu> sysPrivilegeMenus; private List<SysPrivilegeButton> sysPrivilegeButtons; /** * 当用户登录成功后,SpringSecurity会调用此方法获取已登录用户的权限列表 */ @Override public Collection<? extends GrantedAuthority> getAuthorities() { List<GrantedAuthority> grantedAuthorities = new ArrayList<>(); if (this.sysPrivilegeMenus != null) { this.sysPrivilegeMenus.forEach(sysPrivilegeMenu -> { GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(sysPrivilegeMenu.buildPrivilegeString()); grantedAuthorities.add(grantedAuthority); }); } if (this.sysPrivilegeButtons != null) { this.sysPrivilegeButtons.forEach(sysPrivilegeButton -> { GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(sysPrivilegeButton.buildPrivilegeString()); grantedAuthorities.add(grantedAuthority); }); } return grantedAuthorities; } @Override public boolean isAccountNonExpired() { if (this.status == -3) { return false; } return true; } @Override public boolean isAccountNonLocked() { if (this.status == -2) { return false; } return true; } @Override public boolean isCredentialsNonExpired() { if (this.status == -4) { return false; } return true; } @Override public boolean isEnabled() { if (this.status == -1) { return false; } return true; } public String getId() { return id; } public void setId(String 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 Integer getStatus() { return status; } public void setStatus(Integer status) { this.status = status; } public List<SysRole> getSysRoles() { return sysRoles; } public void setSysRoles(List<SysRole> sysRoles) { this.sysRoles = sysRoles; } public List<SysPrivilegeMenu> getSysPrivilegeMenus() { return sysPrivilegeMenus; } public void setSysPrivilegeMenus(List<SysPrivilegeMenu> sysPrivilegeMenus) { this.sysPrivilegeMenus = sysPrivilegeMenus; } public List<SysPrivilegeButton> getSysPrivilegeButtons() { return sysPrivilegeButtons; } public void setSysPrivilegeButtons(List<SysPrivilegeButton> sysPrivilegeButtons) { this.sysPrivilegeButtons = sysPrivilegeButtons; } }
三、创建服务类SysUserDetailsService,该类必须继承SpringSecurity提供的UserDetailsService类:
package com.example.service; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import com.example.dao.SysUserDAO; import com.example.entity.SysPrivilegeButton; import com.example.entity.SysPrivilegeMenu; import com.example.entity.SysRole; import com.example.entity.SysUser; @Service public class SysUserDetailsService implements UserDetailsService { @Autowired private SysUserDAO sysUserDAO; /** * 当登录时,SpringSecurity会调用此方法获取将要认证的用户信息。这里有个问题,只要此用户存在,就会去加载用户的角色权限信息,但此时如果用户是禁用状态,这一步就是多余的,这是一个框架不完善的地方 */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { SysUser dbSysUser = sysUserDAO.findByUsername(username); if (dbSysUser == null) { throw new UsernameNotFoundException("User does not exist"); } dbSysUser = loadRolesAndPrivileges(dbSysUser); return dbSysUser; } public SysUser loadRolesAndPrivileges(SysUser loginedUser) { List<SysRole> sysRoles = sysUserDAO.findRolesById(loginedUser.getId()); List<SysPrivilegeMenu> sysPrivilegeMenus = sysUserDAO.findPrivilegeMenusById(loginedUser.getId()); List<SysPrivilegeButton> sysPrivilegeButtons = sysUserDAO.findPrivilegeButtonsById(loginedUser.getId()); loginedUser.setSysRoles(sysRoles); loginedUser.setSysPrivilegeMenus(sysPrivilegeMenus); loginedUser.setSysPrivilegeButtons(sysPrivilegeButtons); return loginedUser; } }
四、WebMvcConfig.java的配置如下:
package com.example.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addRedirectViewController("/", "/main"); registry.addViewController("/auth/login").setViewName("login"); } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry .addResourceHandler("/static/**") .addResourceLocations("classpath:/static/") .setCachePeriod(31556926); } }
五、WebSecurityConfig.java的配置如下:
package com.example.config; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.cuckoo.universal.utils.web.ResponseEntity; import org.cuckoo.universal.utils.web.ResponseUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AccountExpiredException; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.CredentialsExpiredException; import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.LockedException; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.logout.LogoutHandler; import com.example.entity.SysUser; import com.example.service.SysUserDetailsService; @Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private SysUserDetailsService sysUserDetailsService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider(); authenticationProvider.setUserDetailsService(sysUserDetailsService); authenticationProvider.setHideUserNotFoundExceptions(false); authenticationProvider.setPasswordEncoder(new PasswordEncoder() { /* 密码加密逻辑 */ @Override public String encode(CharSequence charSequence) { PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); return passwordEncoder.encode(charSequence); } /* 密码比对逻辑 */ @Override public boolean matches(CharSequence charSequence, String dbPassword) { PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); return passwordEncoder.matches(charSequence, dbPassword); } }); auth.authenticationProvider(authenticationProvider); } @Override protected void configure(HttpSecurity http) throws Exception { /* 禁用CSRF防护 */ http.csrf().disable(); /* 禁用会话管理,比如当使用token认证时 */ //http.sessionManagement().disable(); /* 访问路径控制 */ http .authorizeRequests() .antMatchers("/auth/login", "/auth/login/verify").permitAll() .antMatchers("/sys/user/**").hasAuthority("MENU-系统管理-用户管理") .antMatchers("/sys/role/**").hasAuthority("MENU-系统管理-角色管理") .anyRequest().authenticated(); /* 登录处理 */ http .formLogin() .loginPage("/auth/login") .loginProcessingUrl("/auth/login/verify") .usernameParameter("username") .passwordParameter("password") .successHandler(new AuthenticationSuccessHandler() { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { SysUser loginedUser = (SysUser) authentication.getPrincipal(); String msg = ResponseEntity.create().success().message("登录成功").put("loginedUser", loginedUser).asJSON(); ResponseUtils.writeJson(msg, response); } }) .failureHandler(new AuthenticationFailureHandler() { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException) throws IOException, ServletException { System.out.println("Reasons for login failure: "+authenticationException.getMessage()); String message = null; if (authenticationException.fillInStackTrace() instanceof UsernameNotFoundException) { message = "用户不存在"; } else if (authenticationException.fillInStackTrace() instanceof BadCredentialsException) { message = "输入密码错误"; } else if (authenticationException.fillInStackTrace() instanceof DisabledException) { message = "该用户帐号已被禁用"; } else if (authenticationException.fillInStackTrace() instanceof LockedException) { message = "该用户帐号已被锁定"; } else if (authenticationException.fillInStackTrace() instanceof AccountExpiredException) { message = "该用户帐号已过期"; } else if (authenticationException.fillInStackTrace() instanceof CredentialsExpiredException) { message = "该用户凭证已过期"; } String msg = ResponseEntity.create().failure().message(message).asJSON(); ResponseUtils.writeJson(msg, response); } }); /* 注销处理 */ http .logout() .logoutUrl("/auth/logout") .addLogoutHandler(new LogoutHandler() { @Override public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { try { String msg = ResponseEntity.create().success().message("注销成功").asJSON(); ResponseUtils.writeJson(msg, response); } catch (Exception e) { e.printStackTrace(); } } }); } @Override public void configure(WebSecurity web) throws Exception { /* 放行静态资源(此处只能表明SpringSecurity不拦截,仍需要配置静态资源访问路径) */ web.ignoring().antMatchers("/static/**"); } }
六、application.properties的配置如下(此处并没有涉及到SpringSecurity的配置信息):
################################### ## SPRING MVC (WebMvcProperties) ## ################################### # Whether a "NoHandlerFoundException" should be thrown if no Handler was found to process a request. spring.mvc.throw-exception-if-no-handler-found = true #################################################### ## SPRING RESOURCES HANDLING (ResourceProperties) ## #################################################### # Whether to enable default resource handling. spring.resources.add-mappings = false ############################################ ## THYMELEAF (ThymeleafAutoConfiguration) ## ############################################ # Whether to enable Thymeleaf view resolution for Web frameworks. spring.thymeleaf.enabled = true # Whether to enable template caching. spring.thymeleaf.cache = false
七、其它文件代码参考
1、登录页面:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>login</title> </head> <body> <form id="LoginForm" th:action="@{/auth/login/verify}" method="post"> <input type="text" name="username" value="" autocomplete="off"/> <input type="password" name="password" value="" autocomplete="off"/> <button type="button" onclick="login()">登录</button> </form> <!-- Script Files --> <script type="text/javascript" th:src="@{/static/thirdparty/jquery/jquery-3.3.1.min.js}"></script> <script type="text/javascript" th:src="@{/static/thirdparty/jquery/form/jquery.form.min.js}"></script> <script type="text/javascript"> function login(){ $("#LoginForm").ajaxSubmit(function(data){ alert(data.message); if(data.success){ window.location.href = "/main"; } }); } </script> </body> </html>
2、主页面控制器:
package com.example.controller; import java.util.Collection; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import com.example.entity.SysUser; @Controller @RequestMapping("/main") public class MainController { @GetMapping public String main(Model model) { /* 获取当前已登录用户,相当于Shiro中的SecurityUtils.getSubject(),随处可用 */ SysUser loginedUser = (SysUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); Collection<? extends GrantedAuthority> grantedAuthorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities(); System.out.println("登录用户:"+loginedUser.getUsername()+",已分配的权限:"+grantedAuthorities); model.addAttribute("loginedUser", loginedUser); model.addAttribute("grantedAuthorities", grantedAuthorities); return "main"; } }
3、主页面:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>main</title> </head> <body> <h5 th:text="${'LoginedUser: '+loginedUser.username}"></h5> <h5>GrantedAuthorities:</h5> <ul th:each="grantedAuthority: ${grantedAuthorities}"> <li th:text="${grantedAuthority}"></li> </ul> <button type="button" onclick="logout()">注销</button> <!-- Script Files --> <script type="text/javascript" th:src="@{/static/thirdparty/jquery/jquery-3.3.1.min.js}"></script> <script type="text/javascript" th:src="@{/static/thirdparty/jquery/form/jquery.form.min.js}"></script> <script type="text/javascript"> function logout(){ $.ajax({ "url": "/auth/logout", "method": "post", "success": function(data){ alert(data.message); if(data.success){ window.location.href = "/auth/login"; } } }); } </script> </body> </html>
八、项目结构参考:


浙公网安备 33010602011771号