Spring Security介绍
大部分系统,都需要认证和鉴权的功能。SpringBoot常用的安全框架spring security和shiro。
shiro相对来说简单易用,spring security功能更完善一点。
本文介绍spring security的集成方法,以及使用数据库维护权限数据,包括用户、权限
使用数据库维护用户数据
数据库表设计
这里简化一下,直接实现User和Role的映射,而省略了Role和资源的映射。
CREATE TABLE `Users` (
`UserId` int(11) NOT NULL AUTO_INCREMENT,
`UserName` varchar(45) NOT NULL,
`PassWord` varchar(100) NOT NULL,
`LockedFlag` tinyint(4) NOT NULL,
PRIMARY KEY (`UserId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `UserRole` (
`UserRoleId` int(11) NOT NULL AUTO_INCREMENT,
`UserId` int(11) NOT NULL,
`RoleId` int(11) NOT NULL,
PRIMARY KEY (`UserRoleId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `Role` (
`RoleId` int(11) NOT NULL AUTO_INCREMENT,
`RoleCode` varchar(45) NOT NULL,
`RoleDesc` varchar(200) DEFAULT NULL,
PRIMARY KEY (`RoleId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Mybatis代码
如果你看过前几章mybatis方面的内容,可以直接自己实现一下,跳过Mybatis这部分内容。
bean层
//RoleBean
package com.it_laowu.springbootstudy.springbootstudydemo.bean;
import java.io.Serializable;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
@SuppressWarnings("serial")
public class RoleBean implements Serializable {
private int roleId;
private String roleCode;
private String roleDesc;
}
//UserBean
......省略package和import
@Data
@Accessors(chain = true)
@SuppressWarnings("serial")
public class UserBean implements Serializable {
private int userId;
private String userName;
private String passWord;
private String lockedFlag;
}
//UserRoleBean
......省略package和import
@Data
@Accessors(chain = true)
@SuppressWarnings("serial")
public class UserRoleBean implements Serializable {
private int userRoleId;
private int userId;
private int roleId;
}
Condition层
//RoleCondition
package com.it_laowu.springbootstudy.springbootstudydemo.bean.condition;
import com.it_laowu.springbootstudy.springbootstudydemo.bean.RoleBean;
import com.it_laowu.springbootstudy.springbootstudydemo.core.base.BaseCondition;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class RoleCondition extends BaseCondition {
private int roleId;
private String roleCode;
private String roleDesc;
@Override
public Class<?> getChildClass() {
return RoleBean.class;
}
}
//UserCondition
......省略package和import
@Data
@Accessors(chain = true)
public class UserCondition extends BaseCondition {
private int userId;
private String userName;
private String passWord;
private String lockedFlag;
@Override
public Class<?> getChildClass() {
return UserBean.class;
}
}
//UserRoleCondition
......省略package和import
@Data
@Accessors(chain = true)
public class UserRoleCondition extends BaseCondition {
private int userRoleId;
private int userId;
private int roleId;
@Override
public Class<?> getChildClass() {
return UserRoleBean.class;
}
}
dao层
//RoleDao
package com.it_laowu.springbootstudy.springbootstudydemo.dao;
import java.util.List;
import com.it_laowu.springbootstudy.springbootstudydemo.bean.RoleBean;
import com.it_laowu.springbootstudy.springbootstudydemo.bean.condition.RoleCondition;
import com.it_laowu.springbootstudy.springbootstudydemo.core.base.IBaseDao;
import org.apache.ibatis.annotations.Param;
public interface RoleDao extends IBaseDao<RoleBean,RoleCondition> {
List<RoleBean> getUserRolesByUserId(@Param("keyId") int userId);
}
//UserDao
......省略package和import
public interface UserDao extends IBaseDao<UserBean,UserCondition> {
UserBean findByName(@Param("username") String username);
}
//UserRoleDao
......省略package和import
public interface UserRoleDao extends IBaseDao<UserRoleBean,UserRoleCondition> {
}
service层
//IRoleService
package com.it_laowu.springbootstudy.springbootstudydemo.service;
import com.it_laowu.springbootstudy.springbootstudydemo.bean.RoleBean;
import com.it_laowu.springbootstudy.springbootstudydemo.bean.condition.RoleCondition;
import com.it_laowu.springbootstudy.springbootstudydemo.core.base.IBaseService;
public interface IRoleService extends IBaseService<RoleBean,RoleCondition> {
List<RoleBean> getUserRolesByUserId(@Param("keyId") int userId);
}
//IUserRoleService
......省略package和import
public interface IUserRoleService extends IBaseService<UserRoleBean,UserRoleCondition> {
}
//IUserService
......省略package和import
public interface IUserService extends IBaseService<UserBean,UserCondition> {
UserBean findByName(@Param("username") String username);
}
ServiceImpl层
//RoleServiceImpl
package com.it_laowu.springbootstudy.springbootstudydemo.service.impl;
import java.util.List;
import com.it_laowu.springbootstudy.springbootstudydemo.bean.RoleBean;
import com.it_laowu.springbootstudy.springbootstudydemo.bean.condition.RoleCondition;
import com.it_laowu.springbootstudy.springbootstudydemo.core.base.IBaseDao;
import com.it_laowu.springbootstudy.springbootstudydemo.dao.RoleDao;
import com.it_laowu.springbootstudy.springbootstudydemo.service.IRoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class RoleServiceImpl implements IRoleService {
@Autowired
public RoleDao roleDao;
@Override
public IBaseDao<RoleBean, RoleCondition> getBaseDao() {
return roleDao;
}
@Override
public List<RoleBean> getUserRolesByUserId(int userId) {
return roleDao.getUserRolesByUserId(userId);
}
}
//UserRoleServiceImpl
......省略package和import
@Service
public class UserRoleServiceImpl implements IUserRoleService {
@Override
public IBaseDao<UserRoleBean, UserRoleCondition> getBaseDao() {
return userRoleDao;
}
}
//UserServiceImpl
......省略package和import
@Service
public class UserServiceImpl implements IUserService {
@Autowired
public UserDao userDao;
@Override
public IBaseDao<UserBean, UserCondition> getBaseDao() {
return userDao;
}
@Override
public UserBean findByName(String username) {
return userDao.findByName(username);
}
}
mybatis.xml
role.mybatis.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.it_laowu.springbootstudy.springbootstudydemo.dao.RoleDao">
<resultMap id="RoleResultMap" type="com.it_laowu.springbootstudy.springbootstudydemo.bean.RoleBean">
<result column="roleId" property="roleId"/>
<result column="roleCode" property="roleCode"/>
<result column="roleDesc" property="roleDesc"/>
</resultMap>
<select id="findAll" resultMap="RoleResultMap">
select roleId,roleCode,`roleDesc`
from `Role`
<where>
<if test="conditionQC.roleId != 0">
and roleId = #{conditionQC.roleId}
</if>
<if test="conditionQC.roleCode != null and '' != conditionQC.roleCode">
and roleCode like concat('%',#{conditionQC.roleCode},'%')
</if>
<if test="conditionQC.roleDesc != null and '' != conditionQC.roleDesc">
and roleDesc like concat('%',#{conditionQC.roleDesc},'%')
</if>
</where>
<choose>
<when test="conditionQC.sortSql == null">
Order by roleId
</when>
<otherwise>
${conditionQC.sortSql}
</otherwise>
</choose>
</select>
<select id="findOne" resultMap="RoleResultMap">
select roleId,roleCode,`roleDesc`
from `Role`
where roleId = #{keyId}
</select>
<select id="getUserRolesByUserId" resultMap="RoleResultMap">
select r.roleId,r.roleCode,r.`roleDesc`
from `Role` r
left join `UserRole` ur ON r.RoleId = ur.RoleId
where ur.UserId = #{keyId}
</select>
<insert id="insert" parameterType="com.it_laowu.springbootstudy.springbootstudydemo.bean.RoleBean">
insert into `Role`(roleId,`roleCode`,`roleDesc`)
values(#{roleId},#{roleCode},#{roleDesc})
</insert>
<update id="update" parameterType="com.it_laowu.springbootstudy.springbootstudydemo.bean.RoleBean">
update `Role`
<set>
<if test="roleCode!=null"> `roleCode`=#{roleCode}, </if>
<if test="roleDesc!=null"> `roleDesc`=#{roleDesc}, </if>
</set>
where roleId = #{roleId}
</update>
<delete id="delete" parameterType="int">
delete from `Role` where roleId = #{keyId}
</delete>
</mapper>
user.mybatis.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.it_laowu.springbootstudy.springbootstudydemo.dao.UserDao">
<resultMap id="UserResultMap" type="com.it_laowu.springbootstudy.springbootstudydemo.bean.UserBean">
<result column="userId" property="userId"/>
<result column="userName" property="userName"/>
<result column="passWord" property="passWord"/>
<result column="lockedFlag" property="lockedFlag"/>
</resultMap>
<select id="findAll" resultMap="UserResultMap">
select userId,userName,`passWord`,lockedFlag
from `Users`
<where>
<if test="conditionQC.userId != 0">
and userId = #{conditionQC.userId}
</if>
<if test="conditionQC.userName != null and '' != conditionQC.userName">
and userName like concat('%',#{conditionQC.userName},'%')
</if>
<if test="conditionQC.passWord != null and '' != conditionQC.passWord">
and passWord like concat('%',#{conditionQC.passWord},'%')
</if>
<if test="conditionQC.lockedFlag != -1">
and lockedFlag = #{conditionQC.lockedFlag}
</if>
</where>
<choose>
<when test="conditionQC.sortSql == null">
Order by userId
</when>
<otherwise>
${conditionQC.sortSql}
</otherwise>
</choose>
</select>
<select id="findOne" resultMap="UserResultMap">
select userId,userName,`passWord`,lockedFlag
from `Users`
where userId = #{keyId}
</select>
<select id="findByName" resultMap="UserResultMap">
select userId,userName,`passWord`,lockedFlag
from `Users`
where userName = #{username}
</select>
<insert id="insert" parameterType="com.it_laowu.springbootstudy.springbootstudydemo.bean.UserBean">
insert into `Users`(userId,`userName`,`passWord`,lockedFlag)
values(#{userId},#{userName},#{passWord},#{lockedFlag})
</insert>
<update id="update" parameterType="com.it_laowu.springbootstudy.springbootstudydemo.bean.UserBean">
update `Users`
<set>
<if test="userName!=null"> `userName`=#{userName}, </if>
<if test="passWord!=null"> `passWord`=#{passWord}, </if>
<if test="lockedFlag!=null"> `lockedFlag`=#{lockedFlag}, </if>
</set>
where userId = #{userId}
</update>
<delete id="delete" parameterType="int">
delete from `Users` where userId = #{keyId}
</delete>
</mapper>
UserRole.mybatis.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.it_laowu.springbootstudy.springbootstudydemo.dao.UserRoleDao">
<resultMap id="UserRoleResultMap" type="com.it_laowu.springbootstudy.springbootstudydemo.bean.UserRoleBean">
<result column="userRoleId" property="userRoleId"/>
<result column="userId" property="userId"/>
<result column="roleId" property="roleId"/>
</resultMap>
<select id="findAll" resultMap="UserRoleResultMap">
select userRoleId,userId,`roleId`
from `UserRole`
<where>
<if test="conditionQC.userRoleId != 0">
and userRoleId = #{conditionQC.userRoleId}
</if>
<if test="conditionQC.userId != 0">
and userId = #{conditionQC.userId}
</if>
<if test="conditionQC.roleId != 0">
and roleId = #{conditionQC.roleId}
</if>
</where>
<choose>
<when test="conditionQC.sortSql == null">
Order by userRoleId
</when>
<otherwise>
${conditionQC.sortSql}
</otherwise>
</choose>
</select>
<select id="findOne" resultMap="UserRoleResultMap">
select userRoleId,userId,`RoleId`
from `UserRole`
where userRoleId = #{keyId}
</select>
<insert id="insert" parameterType="com.it_laowu.springbootstudy.springbootstudydemo.bean.UserRoleBean">
insert into `UserRole`(`userId`,`roleId`)
values(#{userId},#{roleId})
</insert>
<update id="update" parameterType="com.it_laowu.springbootstudy.springbootstudydemo.bean.UserRoleBean">
update `UserRole`
<set>
<if test="userId!=0"> `userId`=#{userId}, </if>
<if test="roleId!=0"> `roleId`=#{roleId}, </if>
</set>
where userRoleId = #{userRoleId}
</update>
<delete id="delete" parameterType="int">
delete from `UserRole` where userRoleId = #{keyId}
</delete>
</mapper>
Controller
因为Controller越来越多,而且都是实现Crud的功能,重复使用的频率很高,所以做了个BaseController
package com.it_laowu.springbootstudy.springbootstudydemo.core.base;
import java.util.List;
import com.it_laowu.springbootstudy.springbootstudydemo.core.customexceptions.ConflictException;
import com.it_laowu.springbootstudy.springbootstudydemo.core.pagehelper.PageInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
public abstract class BaseController<Bean,Condition,IService extends IBaseService<Bean,Condition>> {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired(required = false)
public IService baseService;
@RequestMapping(value = "/list", method = RequestMethod.GET)
public List<Bean> orders(Condition condition,PageInfo pageinfo) {
return baseService.findAll(condition,pageinfo);
}
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public Bean order(@PathVariable(value = "id") int id) {
return baseService.findOne(id);
}
@RequestMapping(value = "/add", method = RequestMethod.POST)
public int add(@Validated @RequestBody Bean bean) {
return baseService.insert(bean);
}
@RequestMapping(value = "/{id}", method = RequestMethod.PUT)
public int update(@PathVariable(value = "id") int id, @RequestBody Bean bean) {
setBeanKeyId(bean, id);
return baseService.update(bean);
}
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
public int delete(@PathVariable(value = "id") int id) {
return baseService.delete(id);
}
//其实就是update时,设置一下主键
protected abstract void setBeanKeyId(Bean bean,int keyId) ;
}
有了这个,其他Controller就简单了,比如:
@RestController
@RequestMapping(value = "/user")
public class UserController extends BaseController<UserBean,UserCondition,IUserService>{
@Override
protected void setBeanKeyId(UserBean bean, int keyId) {
bean.setUserId(keyId);
}
}
维护用户数据时,记得加密密码。
这里提供一个密码 admin
,加密后:$2a$10$xQI1JKsTrkemGxEK1mpsAezGQeTDpSNVjfMCLrhgf8ImpCE2hKI1S
maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
springboot具有自动装配机制,只要引入依赖,即使没配置,也会启动spring security
,配置一套默认的权限。
现在启动项目,Console会自动生成用户User
的密码,访问API会弹出登录提示界面,登录后可以自由访问页面。
Security代码
以上都是数据库维护用户权限数据的代码。
下面的代码是集成Spring Security会用到的一些主要的代码:
-
WebSecurityConfigurerAdapter
:实现Security的各种配置 -
AuthenticationProvider
:通过重写authenticate
方法,实现用户的校验。 -
UserDetailsService
:通过重写loadUserByUsername
方法,可以实现在数据库中配置用户权限。 -
MyAuthenticationFailureHandler
、MyAuthenticationSuccessHandler
:登录成功/失败反馈。 -
resources/static/auth/login.html
:自制比较友好的登录页面。 -
resources/static/auth/logout.html
:自制比较友好的登出提示页面。 -
AdminController
:设置了登录权限的一个页面,提供完整的访问测试。
配置文件WebSecurityConfigurerAdapter
这个类提供了Security的各种开关、特性的配置。
首先,新建一个WebSecurityConfigurerAdapter
的实现类:
package com.it_laowu.springbootstudy.springbootstudydemo.core.config;
import com.it_laowu.springbootstudy.springbootstudydemo.core.auth.MyAuthenticationFailureHandler;
import com.it_laowu.springbootstudy.springbootstudydemo.core.auth.MyAuthenticationProvider;
import com.it_laowu.springbootstudy.springbootstudydemo.core.auth.MyAuthenticationSuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyAuthenticationProvider myAuthenticationProvider;
@Autowired
private MyAuthenticationFailureHandler myAuthenticationFailureHandler;
@Autowired
private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(myAuthenticationProvider);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.httpBasic()
.and()
.authorizeRequests()
.antMatchers("/user/**").permitAll()
.antMatchers("/role/**").permitAll()
.antMatchers("/userrole/**").permitAll()
.antMatchers("/order/**").permitAll()
.antMatchers("/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
//默认是/login,不用改
.loginPage("/login")
.successHandler(myAuthenticationSuccessHandler)
.failureHandler(myAuthenticationFailureHandler)
.permitAll()
.and()
.logout()
//默认为/logout,不用改
.logoutUrl("/logout")
.logoutSuccessUrl("/auth/logout.html") //默认跳转到 /login
.deleteCookies("JSESSIONID"); //默认也是会删除cookie的
}
}
}
针对这部分代码做一些简单的说明:
-
authorizeRequests
:规定了哪些页面需要特定权限,哪些需要用户登录,哪些不需要。 -
formLogin
:定义了API登录页面,以及成功失败的反馈处理(Handler) -
logout
:和login一样,大部分使用默认参数即可。 -
passwordEncoder
:注入一个加密器,之前提供的密码,也是用这个加密器加密的。
认证相关代码
MyAuthenticationProvider
:用户认证
package com.it_laowu.springbootstudy.springbootstudydemo.core.auth;
......省略import
@Component
public class MyAuthenticationProvider implements AuthenticationProvider {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Resource
private MyUserDetailsService myUserDetailsService;
@Resource
private PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//根据输入的用户密码,读取数据库中信息
String username = authentication.getName();
String password = (String) authentication.getCredentials();
MyUserDetails user = (MyUserDetails) myUserDetailsService.loadUserByUsername(username);
//判断是否有效用户
if (!user.isEnabled()) {
throw new DisabledException("USER DISABLE");
} else if (!user.isAccountNonLocked()) {
throw new LockedException("USER LOCKED");
} else if (!user.isAccountNonExpired()) {
throw new AccountExpiredException("USER EXPIRED");
} else if (!user.isCredentialsNonExpired()) {
throw new CredentialsExpiredException("USER LOGOUT");
}
//验证密码
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new BadCredentialsException("PASSWORD INVALID!");
}
logger.info(String.format("用户%s登录成功", username));
return new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities());
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
MyUserDetailsService
:用户数据处理,这里是和数据库交互的关键点
package com.it_laowu.springbootstudy.springbootstudydemo.core.auth;
......省略import
@Component
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private IUserService userService;
@Autowired
private IRoleService roleService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserBean bean = userService.findByName(username);
if (bean == null){
throw new UsernameNotFoundException("User "+ username +" didn't exist.");
}
//获得角色信息
List<RoleBean> roles = roleService.getUserRolesByUserId(bean.getUserId());
//格式转化
List<GrantedAuthority> authority = roles.stream().map(i->new SimpleGrantedAuthority(i.getRoleCode())).collect(Collectors.toList());
return new MyUserDetails(bean.getUserName(),bean.getPassWord(),bean.getLockedFlag()==1,authority);
}
}
MyUserDetails
:UserDetails实现类,保存数据
package com.it_laowu.springbootstudy.springbootstudydemo.core.auth;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
@SuppressWarnings("serial")
public class MyUserDetails implements UserDetails {
protected Integer id;
private String username;
private String password;
private boolean lockedFlag;
public MyUserDetails(String username, String password, boolean lockedFlag,Collection<? extends GrantedAuthority> roles) {
this.username = username;
this.password = password;
this.lockedFlag = lockedFlag;
this.authorities = roles;
}
public Collection<? extends GrantedAuthority> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return !lockedFlag;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
MyAuthenticationSuccessHandler
:认证成功反馈
package com.it_laowu.springbootstudy.springbootstudydemo.core.auth;
......省略import
@Component
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler{
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
//登录成功返回
ResultBody resultBody = new ResultBody("200", "登录成功");
//设置返回请求头
response.setContentType("application/json;charset=utf-8");
//写出流
PrintWriter out = response.getWriter();
ObjectMapper mapper = new ObjectMapper();
out.write(mapper.writeValueAsString(resultBody));
out.flush();
out.close();
}
}
MyAuthenticationFailureHandler
:认证失败反馈
package com.it_laowu.springbootstudy.springbootstudydemo.core.auth;
......省略import
@Component
public class MyAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
//登录失败信息返回
ResultBody resultBody = new ResultBody("401", "登录失败:"+exception.getLocalizedMessage());
//设置返回请求头
response.setContentType("application/json;charset=utf-8");
//写出流
PrintWriter out = response.getWriter();
ObjectMapper mapper = new ObjectMapper();
out.write(mapper.writeValueAsString(resultBody));
out.flush();
out.close();
}
}
杂项代码
/resources/static/auth/login.html
就是提供一个友好的登录页面,方便测试。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<h2>Login</h2>
<form id="loginForm" action="/login" method="post">
用户:<input type="text" id="username" name="username"><br /><br />
密码:<input type="password" id="password" name="password"><br /><br />
<button id="btnlogin" type="button">Login</button>
</form>
<span id="msg" name="msg"></h2>
<script src="http://libs.baidu.com/jquery/2.1.4/jquery.min.js"></script>
<script type="text/javascript">
$("#btnlogin").click(function () {
$.ajax({
type: "POST",
url: "/springboot-study-demo/login",
data: $("#loginForm").serialize(),
dataType: "JSON",
success: function (data) {
console.log(data);
$("#msg").text(data.message);
}
});
});
</script>
</body>
</html>
/resources/static/auth/logout.html
登出成功的反馈页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Logout</title>
</head>
<body>
<h2>Log out success !!</h2>
</body>
</html>
AdminController
业务Controller,所有url均需登录用户。
@RestController
@RequestMapping(value = "/admin")
public class AdminController {
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public String info(@PathVariable(value = "id") int id) {
return "i am an administrator";
}
}
测试一下
- 访问
/admin/1
,直接跳转到/login
,访问失败 - 访问
/auth/login.html
,先测密码错误,再测成功。 - 访问
/admin/1
,返回i am an administrator
。 - 访问
/logout
,跳转到/auth/logout.html
. - 访问
/admin/1
,直接跳转到/login
,再次访问失败
这里要注意,部分浏览器(比如Chrome)会自动记录用户密码,导致logout后,再次访问/admin/1
还是成功的。使用火狐就没这个问题。