新建项目
file->new->project->Spring Initalizr->选择依赖
配置application.properties
#端口号(默认8080)
server.port=8083
#关闭thymeleaf缓存,方便调试
spring.thymeleaf.cache=false
#数据源
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springbootdemo?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
#使用druid连接池
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
#自动生成表
spring.jpa.hibernate.ddl-auto=update
thymeleaf默认开启缓存,修改页面后需重启服务。
使用druid连接池需要在pom.xml中加入依赖。
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
创建实体类User、Role、Authority
Authority
@Entity
@Table(uniqueConstraints = {@UniqueConstraint(columnNames = {"name","code"})})
public class Authority {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
//权限名
@Column
private String name;
//标识
@Column
private String code;
//路径
@Column
private String url;
@ManyToMany(mappedBy = "authorities")
private List<Role> roleList=new ArrayList<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public List<Role> getRoleList() {
return roleList;
}
public void setRoleList(List<Role> roleList) {
this.roleList = roleList;
}
}
Role
@Entity
@Table(uniqueConstraints = {@UniqueConstraint(columnNames = {"name"})})
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
//角色名
@Column
private String name;
//权限
@ManyToMany(fetch = FetchType.EAGER)
private List<Authority> authorities=new ArrayList<>();
//用户
@ManyToMany(mappedBy = "roles")
private List<User> userlist=new ArrayList<>();
public List<User> getUserlist() {
return userlist;
}
public void setUserlist(List<User> userlist) {
this.userlist = userlist;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Authority> getAuthorities() {
return authorities;
}
public void setAuthorities(List<Authority> authorities) {
this.authorities = authorities;
}
}
User
@Entity
@Table(uniqueConstraints = {@UniqueConstraint(columnNames = {"userName"})})
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
//用户名
@Column
private String userName;
//密码
@Column
private String password;
//角色
@ManyToMany(fetch = FetchType.EAGER)
private List<Role> roles=new ArrayList<>();
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> grantedAuthorities=new ArrayList<>();
List<Role> roles = this.roles;
for (Role role : roles) {
List<Authority> authorities = role.getAuthorities();
for (Authority authority : authorities) {
grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_"+authority.getCode()));
}
}
return grantedAuthorities;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.userName;
}
// 帐户是否过期
@Override
public boolean isAccountNonExpired() {
return true;
}
// 帐户是否被冻结
@Override
public boolean isAccountNonLocked() {
return true;
}
// 帐户密码是否过期,一般有的密码要求性高的系统会使用到,比较每隔一段时间就要求用户重置密码
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// 帐号是否可用
@Override
public boolean isEnabled() {
return true;
}
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 void setPassword(String password) {
this.password = password;
}
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
@Override
public int hashCode() {
return userName.hashCode();
}
@Override
public boolean equals(Object obj) {
return this.toString().equals(obj.toString());
}
@Override
public String toString() {
return this.userName;
}
User需要实现security的UserDetails接口。
getAuthorities()方法是获取用户的所有权限,因为security匹配的是以ROLE_开头的权限,所以这里拼接了ROLE_。也可以在数据库插入权限的时候加ROLE_前缀。
如果设置了security用户并发登入,还需要重写User的equals、toString、hashCode方法。
创建UserDao、RoleDao、AuthorityDao
public interface UserDao extends JpaRepository<User,Long> {
}
public interface RoleDao extends JpaRepository<Role,Long> {
}
public interface AuthorityDao extends JpaRepository<Authority,Long> {
}
创建UserService、AuthorityService
@Service
public class UserServiceImpl implements UserDetailsService {
@Autowired
private UserDao userDao;
/**
* 查询用户信息
* @param s
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
User user = new User();
user.setUserName(s);
Example<User> of = Example.of(user);
Optional<User> one = userDao.findOne(of);
if (one.isPresent()){
user=one.get();
}
return user;
}
}
@Service
public class AuthorityServiceImpl implements AuthorityService {
@Autowired
private AuthorityDao authorityDao;
@Override
public List<Authority> findAll() {
return authorityDao.findAll();
}
}
UserServiceImpl需要实现UserDetailsService重写其loadUserByUsername方法,根据用户名查询用户
自定义登录成功处理器与登录失败处理器及提示信息
/**
* 登录成功处理器
*/
@Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.getWriter().print("ok");
}
}
/**
* 登录失败处理器
*/
@Component
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
String message="登录异常,请联系管理员";
if (e instanceof UsernameNotFoundException ){
message="用户不存在";
}else if (e instanceof BadCredentialsException ){
message="用户名或密码错误";
}else if (e instanceof AccountExpiredException ){
message="账户过期";
}else if (e instanceof LockedException){
message="账户锁定";
}else if (e instanceof DisabledException ){
message="账户不可用";
}else if (e instanceof CredentialsExpiredException){
message="证书过期";
}
httpServletResponse.getWriter().print(message);
}
}
配置验证和http拦截链
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserServiceImpl userService;
@Autowired
private AuthorityService authorityService;
@Autowired
private MyAuthenticationFailureHandler myAuthenticationFailureHandler;
@Autowired
private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
//配置用户验证,使用BCryptPasswordEncoder加密方式
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
}
//http拦截链
@Override
protected void configure(HttpSecurity http) throws Exception {
//登录
http.formLogin()
.loginPage("/login") // 设置登录页面
.loginProcessingUrl("/login") // 自定义的登录接口
.successHandler(myAuthenticationSuccessHandler)//登录成功处理器
.failureHandler(myAuthenticationFailureHandler)//登录失败处理器
.and()
.csrf().disable();//关闭csrf
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry expressionInterceptUrlRegistry = http.authorizeRequests();
//配置不拦截的请求,其他请求拦截
expressionInterceptUrlRegistry.antMatchers("/login", "/js/**").permitAll();
//配置访问路径需要的权限
List<Authority> all = authorityService.findAll();
for (Authority authority : all) {
expressionInterceptUrlRegistry .antMatchers( authority.getUrl()).hasRole(authority.getCode() );
}
//其他请求,登陆后可以访问
expressionInterceptUrlRegistry .anyRequest().authenticated();
//session
http.sessionManagement()
.maximumSessions(1)//最大并发登录
.expiredUrl("/login");//登录过期后跳转的url
//注销
http.logout()
.logoutUrl("/logout")//触发注销的请求
.logoutSuccessUrl("/login")//注销后跳转的请求
.invalidateHttpSession(true);//使session失效
//编码格式
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
filter.setForceEncoding(true);
http.addFilterBefore(filter, CsrfFilter.class);
}
}
创建LoginController
@Controller
public class LoginController {
@GetMapping("/login")
public String login(){
return "login";
}
@RequestMapping("/")
public String index(){
return "index";
}
}
注意:return的后面不要加/,在idea中加/后可以正常访问,但是在打包运行后会报错
创建login页面和index页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" >
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<span style="color: red" id="msg"></span>
<form>
<div>
<label>用户名:</label><input name="username" id="username">
</div>
<div>
<label>密码:</label><input name="password" id="password">
</div>
<div>
<button type="button" onclick="sub()">提交</button>
</div>
</form>
</body>
<script src="/js/jquery-3.4.1.min.js"></script>
<script type="text/javascript">
function sub() {
var username=$("#username").val();
var password=$("#password").val();
$.post("/login",{username:username,password:password},function (data) {
if (data=="ok"){
window.location.href="/";
}else {
$("#msg").text(data);
}
})
}
</script>
</html>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
首页<br><a th:href="@{/logout}">注销</a>
</body>
</html>
自定义403页面
在templates下创建error文件夹,创建403.html。当出现403错误时会自动跳转到此页面。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
无权限
</body>
</html>