-
相关pom依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
-
如果需要security去帮你做用户认证的话,需让用户实体类去实现UserDetails接口,这里面的type属性代表权限
public class User implements UserDetails {
private int id;
private String username;
private String password;
private String salt;
private String email;
private int type;
private int status;
private String activationCode;
private String headerUrl;
private Date createTime;
@Override //设置账号不过期
public boolean isAccountNonExpired() {
return true;
}
@Override //账号未锁定
public boolean isAccountNonLocked() {
return true;
}
@Override //凭证未过期
public boolean isCredentialsNonExpired() {
return true;
}
@Override //账号可用
public boolean isEnabled() {
return true;
}
@Override //得到权限
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> list = new ArrayList<>();
list.add(new GrantedAuthority() {
@Override
public String getAuthority() {
switch (getType()) {
case 1:
return "ADMIN";
default:
return "USER"; //否则
}
}
});
return list;
}
}
- UserService类,这里需要实现UserDetailsService接口,如果不需要security认证的话,可以不去实现。
@Service
public class UserService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
public User findUserByName(String username) {
return userMapper.selectByName(username);
}
//security需要我们在loadUserByUsername提供一个可以查用户的方法,这里直接返回findUserByName就可以了
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
return this.findUserByName(s);
}
}
- 接下来就可以去写我们的配置类了,SecurityConfig
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserService userService;
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**"); //忽略静态资源的访问
}
//AuthenticationManager 认证的核心接口
//AuthenticationManagerBuilder 用于构建AuthenticationManager对象的工具
//providerManager: AuthenticationManager接口的默认实现类
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//内置的认证规则,这里的盐值是固定的,而实际如果我们有自己的加密规则的话,一般不去使用这个方法
//auth.userDetailsService(userService).passwordEncoder(new Pbkdf2PasswordEncoder("12345"));//加盐加密
//自定义认证规则
//AuthenticationProvider: providerManager持有一组AuthenticationProvider,每个AuthenticationProvider负责一种认证(登录时不只账号密码一种方式,第三方登录也是一种方式,兼容所有的登录方式)
//providerManager将认证委托给AuthenticationProvider
auth.authenticationProvider(new AuthenticationProvider() { //账号密码认证方式
//Authentication 用于封装认证信息(账号密码),不同方式封装不同的认证信息
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = (String) authentication.getCredentials();
User user = userService.findUserByName(username);
if(user == null) {
throw new UsernameNotFoundException("账号不存在"); //security会有filter去接收异常
}
password = WeUtil.md5(password + user.getSalt());//对明文密码进行Md5加盐加密
if (!user.getPassword().equals(password)){
throw new BadCredentialsException("密码不正确");
}
return new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities()); // 认证主要信息, 证书, 权限
}
//返回当前的认证类型
@Override
public boolean supports(Class<?> aClass) {
//UsernamePasswordAuthenticationToken: Authentication接口的常用实现类
return UsernamePasswordAuthenticationToken.class.equals(aClass);
}
});
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//super.configure(http); //覆盖父类的方法,查看源码可以看出security为我们提供了默认的认证界面,如果不覆盖,所有请求都会通过默认的认证,同学们可以去测试一下,引入依赖后security就会立刻接管我们的认证,这里就不展示了
//登录相关的配置
http.formLogin().loginPage("/loginpage") //登录界面
.loginProcessingUrl("/login") //认证的url映射
//若认证成功
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.sendRedirect(request.getContextPath() + "/index"); //重定向,会向客户端传递新的请求,request被重置
}
})
//若认证失败
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
request.setAttribute("error",e.getMessage());
request.getRequestDispatcher("/loginpage").forward(request, response); //转发,客户端可以通过request得到信息
}
});
http.logout().logoutUrl("/logout") //退出
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.sendRedirect(request.getContextPath() + "/index");
}
});
//授权配置
http.authorizeRequests().antMatchers("/letter").hasAnyAuthority("USER","ADMIN") //拥有USER和ADMIN权限的可以请求/letter
.antMatchers("/admin").hasAnyAuthority("ADMIN")
.and().exceptionHandling().accessDeniedPage("/denied"); //权限不够时,返回的请求
//增加filter处理验证码,addFilterBefore,如果验证码错误,则不进行用户名密码的认证
http.addFilterBefore(new Filter() {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
if(request.getContextPath().equals("/login")) {
String code = request.getParameter("vertifyCode");
if(code == null || !code.equalsIgnoreCase("1234") ) { //这里把验证码写死了,只是做一个测试
request.setAttribute("error","验证码错误");
request.getRequestDispatcher("/loginpage").forward(request, response);
return;
}
}
filterChain.doFilter(request, response); //让请求向下执行,去找下一个filter,否则请求到此终止
}
}, UsernamePasswordAuthenticationFilter.class); //UsernamePasswordAuthenticationFilter,在用户名密码验证之前执行
//记住我
http.rememberMe()
.tokenRepository(new InMemoryTokenRepositoryImpl()) //把用户存到内存里,要想存到redis里,需要自己实现接口
.tokenValiditySeconds(3600*24) //3600s*24
.userDetailsService(userService);
}
}
- 加密工具类
public class WeUtil {
public static String md5(String key) {
if (StringUtils.isBlank(key)) {
return null;
}
return DigestUtils.md5DigestAsHex(key.getBytes());
}
}
- IndexController
@Controller
public class IndexController {
@RequestMapping(path = "/index", method = RequestMethod.GET)
public String getIndexPage(Model model) {
//认证成功后,认证结果会通过SecurityContextHolder存入SecurityContext中
Object obj = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (obj instanceof User) {
model.addAttribute("user",obj);
}
return "/index";
}
@RequestMapping(path = "/discuss", method = RequestMethod.GET) //不需要权限即可访问
public String getDiscussPage() {
return "/site/discuss";
}
@RequestMapping(path = "/letter", method = RequestMethod.GET) //有USER权限的可以访问
public String getLetterPage() {
return "/site/letter";
}
@RequestMapping(path = "/admin", method = RequestMethod.GET) //有ADMIN权限的可以访问
public String getAdminPage() {
return "/site/admin";
}
@RequestMapping(path = "/loginpage", method = {RequestMethod.GET, RequestMethod.POST})
public String getLoginPage() {
return "/site/login";
}
//
@RequestMapping(path = "/denied", method = {RequestMethod.GET})
public String getDeniedPage() {
return "/error/404";
}
}
- UserMapper
@Mapper
public interface UserMapper {
User selectByName(String username);
}
- mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.we.community.dao.UserMapper">
<sql id="selectFields">
id, username, password, salt, email, type, status, activation_code, header_url, create_time
</sql>
<select id="selectByName" resultType="User">
select
<include refid="selectFields"></include>
from user
where username = #{username}
</select>
</mapper>
- 首页
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>社区首页</h1>
<p th:if="${user!=null}">
欢迎你, <span th:text="${user.username}"></span>!
</p>
<ul>
<li><a th:href="@{/discuss}">帖子详情</a></li>
<li><a th:href="@{/letter}">私信列表</a></li>
<li><a th:href="@{/loginpage}">登录</a></li>
<!-- security要求退出需用post -->
<li>
<form method="post" th:action="@{/logout}">
<a href="javascript:document.forms[0].submit();">退出</a>
</form>
</li>
</ul>
</body>
</html>
- 登录页面。 admin, discuss, letter界面只是一句话,这里就不贴了
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<h1>登录社区</h1>
<form method="post" th:action="@{/login}">
<p style="color:red;" th:text="${error}">
<!--提示信息-->
</p>
<p>
账号:<input type="text" name="username" th:value="${username}">
</p>
<p>
密码:<input type="password" name="password" th:value="${password}">
</p>
<p>
验证码:<input type="text" name="vertifyCode">
</p>
<p>
<input type="submit" value="登录">
</p>
<p>
<input type="checkbox" name="remember-me"> 记住我
</p>
</form>
</body>
</html>
示例中只用type字段代表了权限,如果需要更复杂的授权时,需要自己设计权限表。