maven
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
SecurityConfig
package com.beetl.fromework.config.security;
import com.beetl.fromework.handle.LoginFailureHandle;
import com.beetl.fromework.handle.LoginSuccessHandler;
import com.beetl.fromework.handle.VerificationCodeFiler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import javax.sql.DataSource;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private DataSource dataSource;
@Autowired
private LoginFailureHandle loginFailureHandle;
@Autowired
private LoginSuccessHandler loginSuccessHandler;
@Autowired
private VerificationCodeFiler verificationCodeFiler;
@Autowired
private CustomSessionInformationExpiredStrategy sessionInformationExpiredStrategy;
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
return jdbcTokenRepository;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(daoAuthenticationProvider());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/css/**",
"/cert/**",
"/register",
"/register.html",
"/",
"/fontawesome/**",
"/images/**",
"/js/**",
"/login.html",
"/index/html",
"/captchaImage",
"/getPhoneCode/**",
"/regist",
"/category/**").permitAll();
http.formLogin()
.loginPage("/login")
.loginProcessingUrl("/userLogin")
.usernameParameter("username")
.passwordParameter("password")
.failureHandler(loginFailureHandle)
.successHandler(loginSuccessHandler)
.and()
.sessionManagement()
.invalidSessionUrl("/invalid")
.maximumSessions(1)
.expiredSessionStrategy(sessionInformationExpiredStrategy);
http.authorizeRequests()
.anyRequest().authenticated();
http.csrf().disable();
http.exceptionHandling().accessDeniedPage("/403");
http.logout().logoutUrl("/logout").logoutSuccessUrl("/login").permitAll();
http.rememberMe()
.rememberMeParameter("rememberMe")
.tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(30*60)
.userDetailsService(userDetailsService);
http.addFilterBefore( verificationCodeFiler,UsernamePasswordAuthenticationFilter.class);
}
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider(){
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setHideUserNotFoundExceptions(false);
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
daoAuthenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder());
return daoAuthenticationProvider;
}
}
UserDetailServiceImpl
package com.beetl.fromework.config.security;
import com.baomidou.mybatisplus.extension.service.additional.query.impl.LambdaQueryChainWrapper;
import com.beetl.common.core.domain.entity.SysRole;
import com.beetl.common.core.domain.entity.SysUser;
import com.beetl.pc.mapper.SysRoleMapper;
import com.beetl.pc.mapper.SysUserMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
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 org.springframework.util.StringUtils;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
@Service
@Slf4j
public class UserDetailServiceImpl implements UserDetailsService
{
@Resource
private SysUserMapper userMapper;
@Resource
private SysRoleMapper sysRoleMapper;
@Resource
private HttpServletRequest request;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
{
if (StringUtils.isEmpty(username))
{
request.getSession().setAttribute("hintMsg","您输入的手机号不能为空");
}
SysUser sysUserInfo = new LambdaQueryChainWrapper<>(userMapper)
.eq(SysUser::getUserName,username)
.one();
if (sysUserInfo == null)
{
request.getSession().setAttribute("hintMsg","您输入的手机号不存在");
}
List<SysRole> roleList = new LambdaQueryChainWrapper<>(sysRoleMapper)
.eq(SysRole::getRoleId, sysUserInfo.getUserId())
.list();
List<GrantedAuthority> authorities = new ArrayList<>();
roleList.forEach(sysRole ->
{
authorities.add(new SimpleGrantedAuthority(sysRole.getRoleKey()));
});
return new User(sysUserInfo.getUserName(), sysUserInfo.getPassword(), authorities);
}
}
设置只能在线一个账号 CustomSessionInformationExpiredStrategy
package com.beetl.fromework.config.security;
import com.beetl.common.HttpStatus;
import org.springframework.security.web.session.SessionInformationExpiredEvent;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class CustomSessionInformationExpiredStrategy implements SessionInformationExpiredStrategy
{
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException
{
HttpServletResponse response = event.getResponse();
response.setStatus(HttpStatus.UNAUTHORIZED);
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("你的账号已经在别的地方登录,当前登录已失效,如果密码遭到泄露,请立即修改密码");
}
}
自定义成功登录处理逻辑 LoginFailureHandle
package com.beetl.fromework.handle;
import com.beetl.common.core.domain.model.LoginBody;
import com.beetl.common.utils.RequestUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@Component
@Slf4j
public class LoginSuccessHandler implements AuthenticationSuccessHandler
{
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException
{
HttpSession session = request.getSession();
session.setAttribute("request",request);
returnSuccessPage(request,response,authentication);
}
private void returnSuccessPage(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException
{
String strUrl = request.getContextPath()+"/";
String account = request.getParameter("username");
RequestUtil.sessionRemoveAttribute(request,new String[]{"msg"});
request.getSession().setAttribute("msg",account+"登陆成功");
LoginBody loginUser = new LoginBody();
loginUser.setAccount(account);
request.getSession().setAttribute("LoginUserInfo",loginUser);
log.info("登录成功,页面跳转:"+strUrl);
response.sendRedirect(strUrl);
}
}
自定义失败登录处理逻辑 LoginFailureHandle
package com.beetl.fromework.handle;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
@Slf4j
public class LoginFailureHandle extends SimpleUrlAuthenticationFailureHandler
{
@Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException
{
returnErrorPage(request,response,exception);
}
private void returnErrorPage(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception) throws IOException
{
String strUrl = request.getContextPath()+"/login";
String errorMsg = exception.getMessage();
if ("Bad credentials".equals(exception.getMessage()))
{
errorMsg = "您输入的密码错误";
}
request.getSession().setAttribute("hintMsg",errorMsg);
log.info("登陆失败,页面跳转:" + strUrl);
response.sendRedirect(strUrl);
}
}
验证码拦截器 VerificationCodeFiler
package com.beetl.fromework.handle;
import com.beetl.common.Constants;
import com.beetl.common.core.redis.RedisCache;
import com.beetl.common.utils.RequestUtil;
import com.beetl.common.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.server.Session;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@Component
@Slf4j
public class VerificationCodeFiler extends OncePerRequestFilter implements InitializingBean
{
@Autowired
private RedisCache redisCache;
private static String url="/userLogin";
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException
{
String uri = request.getRequestURI();
Boolean flag= true;
if (url.equals(uri))
{
flag = checkImageCode(request, response);
}
if (flag){
filterChain.doFilter(request, response);
}
}
private Boolean checkImageCode(HttpServletRequest request,HttpServletResponse response) throws IOException
{
Cookie[] cookies = request.getCookies();
String uuid = "";
for (Cookie cookie : cookies)
{
String cookieName = cookie.getName();
if ("captcha".equals(cookieName))
{
uuid = cookie.getValue();
}
}
String redisImageCode = redisCache.getCacheObject(Constants.CAPTCHA_CODE_KEY + uuid);
String imageCode = request.getParameter("imageCode");
RequestUtil.sessionRemoveAttribute(request,new String[]{"codeMsg"});
String strUrl = request.getContextPath()+"/login";
boolean flag = true;
if (StringUtils.isEmpty(redisImageCode) || StringUtils.isEmpty(imageCode))
{
RequestUtil.sessionSetAttribute(request,"hintMsg","验证码不能为空");
redisCache.deleteObject(Constants.CAPTCHA_CODE_KEY + uuid);
flag = false;
response.sendRedirect(strUrl);
}
if (!imageCode.equalsIgnoreCase(redisImageCode))
{
RequestUtil.sessionSetAttribute(request,"hintMsg","您输入的验证码有误");
redisCache.deleteObject(Constants.CAPTCHA_CODE_KEY + uuid);
flag = false;
response.sendRedirect(strUrl);
}
return flag;
}
}
Controller 权限注解
@Secured({
"ROLE_demand", "ROLE_developer", "ROLE_company_developer", "ROLE_company_demand", "ROLE_person_developer", "ROLE_person_demand"
})
@GetMapping("/myTeam")
public String myTeam(HttpServletRequest request)
{
return "/user/person.html#projects";
}
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="${beetlSeo.seoDescription!}">
<meta name="keywords" content="${beetlSeo.seoKeywords!}">
<title>${beetlSeo.seoTitle!}</title>
<link href="${ctxPath}/images/favicon.ico" rel="SHORTCUT ICON">
<link rel="stylesheet" type="text/css" href="${ctxPath}/css/bootstrap.css">
<link rel="stylesheet" type="text/css" href="${ctxPath}/css/login.css">
<link rel="stylesheet" type="text/css" href="${ctxPath}/fontawesome/css/font-awesome.min.css">
<link rel="stylesheet" type="text/css" href="${ctxPath}/css/message.css">
<style>
body {
background-image: url("/images/login-background.jpg");
}
</style>
</head>
<body>
<nav class=" " style="background-color: #ffcc00;">
<div class="container p-0">
<div class="row" style="height: 100px;">
<div class="col-md-10 p-0 d-flex align-items-center">
<img src="${ctxPath}/images/beetl-title.png" class="w-25 h-50 img-fluid" alt="mifeng">
</div>
<div class="col-md-1 p-0 d-flex justify-content-center align-items-center login-front ">
<a class="text-decoration-none" href="/login">登录</a>
</div>
<div class="col-md-1 p-0 d-flex justify-content-center align-items-center login-front">
<a class="text-decoration-none" href="/register">注册</a>
</div>
</div>
</div>
</nav>
<nav class="login-container">
<div class="login-row">
<div class="row-one">
<span class="welcome_container">欢迎登录</span>
<a href="/register" class="regist-container">还没注册账号?</a>
</div>
<div class="row-two">手机号登录</div>
<form action="/userLogin" method="post" onsubmit="return validateForm()">
<div class="row-three">
<input type="text" style="outline: none;" class="row-phone" placeholder="请输入手机号" name="username"
id="userPhone">
<%if(has(userPhoneMsg))
{%>
<span class="d-block my-1 pl-1" class="check-userPhone" style="font-size: .8rem ;color: red;">
用户名不存在
</span>
<%}else{%>
<span class="d-block my-1 pl-1" style="font-size: .8rem ;color: red;visibility: hidden" class="check-userPhone">
校验手机号
</span>
<%}%>
<input type="password" style="outline: none;" class="row-password mt-0" placeholder="请输入密码"
name="password" id="password">
<%if(has(passwordMsg)){%>
<span class="d-block my-1 pl-1 check-password"
style="font-size: .8rem ;color: red;" >密码错误 </span>
<%}else{%>
<span class="d-block my-1 pl-1 check-password"
style="font-size: .8rem ;color: red;visibility: hidden" >密码必须为包含大小写字母和数字的组合,长度在6-10之间 </span>
<%}%>
<div class="row-three-one d-flex ">
<input type="text" class="row-phone row-three-one-text" style="height: 47px;width: 300px"
placeholder="请输入图像验证码" name="imageCode" id="code">
<img alt="点击图片刷新" id="codeImg" onclick="getImageCode()"
class="ml-2"
style="height: 47px;width: 150px; border: 2px #999999 solid;border-radius: 6px;" >
</div>
<span class="d-block my-1 pl-1" style="font-size: .8rem ;color: red;visibility: hidden" class="check-code " >校验验证码</span>
</div>
<div class="row-four">
<input class="remmberme " type="checkbox" name="rememberMe">
<span class="remmberme-password">记住我</span>
</div>
<input type="text" name="uuid" style="visibility: hidden;height: .1rem" id="uuid">
<div class="row-five mt-1">
<input type="submit" class="login-button" value="登录">
</div>
</form>
</div>
</nav>
<div class="foot-container">
<div class="foot-one">
<span>服务电话:</span>
<span>18769854231</span>
<span class="email-container">合作邮箱:</span>
<span>yidong@yidong.com</span>
<span class="email-container">Copyright2023有限公司ICP备xxxxx号</span>
</div>
</div>
<script src="${ctxPath}/js/jquery.min.js"></script>
<script src="${ctxPath}/js/bootstrap.bundle.js"></script>
<script src="${ctxPath}/js/message.js" type="text/javascript" charset="utf-8"></script>
<script src="${ctxPath}/js/login.js" type="text/javascript"></script>
<script type="text/javascript">
function responseMsg(){
if('${session.hintMsg}' !== '') {
$.message({type: 'error', content: '${session.hintMsg!}'});
${@com.beetl.common.utils.RequestUtil.sessionRemove(request,'hintMsg')}
}
}
</script>
</body>
</html>